Beispiel #1
0
class TransactionManager(object):
    
    def __init__(self, undo_log='undo.log', redo_log='redo.log', dt_log='distributed_transaction.log', 
                 server=None, replica_uri_list=None):
        self.server = server
        self._datastore = DataStore()
        self._prevTransactionIndex = None
        self.currTransactionIndex = None
        
        self._undoLog = Log(undo_log)
        self._redoLog = Log(redo_log)
        self._DTLog = Log(dt_log)
        
        # Create connection with replicas
        self._replicas = []
        self._replicaURIs = []
        #self._votes = {}
        if replica_uri_list is not None:
            for uri in replica_uri_list:
                if uri.find(self.server) == -1:
                    self._replicaURIs.append(uri)
                    self._replicas.append(Client(uri, cache=NoCache()))
        
        # check logs in case we need recovery
        #self.check_logs()
    
    def set_server(self, server):
        logging.info('TM: set_server() called.')
        self.server = server
    
    def add_replica(self, replica_uri):
        logging.info('TM: add_replica() called.')
        if len(self._replicas)==0: 
            logging.debug('TM: replica added: %s', replica_uri)
            self._replicas = []
            self._replicaURIs = []
            self._replicaURIs.append(replica_uri)
            self._replicas.append(Client(replica_uri, cache=NoCache()))
        elif replica_uri not in self._replicaURIs:
            logging.debug('TM: replica added: %s', replica_uri)
            self._replicaURIs.append(replica_uri)
            self._replicas.append(replica_uri)
    
    def tpc_begin(self):
        # Create new transaction
        if self.currTransactionIndex is not None:
            return False
        else:
            logging.info('TM: new trx')
            if self._prevTransactionIndex is None:
                self.currTransactionIndex = 1
            else:
                self.currTransactionIndex = self._prevTransactionIndex + 1
            return True
    
    def tpc_vote_replica(self, msg):
        msg = eval(msg)
        logging.info('R: msg: %s', msg)
        # Check if a trx is already in process
        if self.currTransactionIndex is not None \
            and self.currTransactionIndex != int(msg['operation'][0]):
            logging.info('R: Another trx in process. Sending VOTE NO')
            DTEntry = [DTLogMessage.VOTEDNO, str(self.currTransactionIndex)]
            self._DTLog.write(DTEntry)
            return False
        
        # Check if trx if behind
        #TODO: Handle greater_than & less_than separately?
        if self._prevTransactionIndex is not None \
            and self._prevTransactionIndex+1 != int(msg['operation'][0]): 
            logging.info('R: trx is behind the coordinator. Sending VOTE NO')
            DTEntry = [DTLogMessage.VOTEDNO, str(self.currTransactionIndex)]
            self._DTLog.write(DTEntry)
            return False
        
        # Begin Trx
        status = self.tpc_begin()
        if status == False:
            logging.info('R: unable to begin trx. Sending VOTE NO')
            DTEntry = [DTLogMessage.VOTEDNO, str(self.currTransactionIndex)]
            self._DTLog.write(DTEntry)
            return False
        else:
            # Write entry into undo log
            undoEntry = msg['operation']
            self._undoLog.write(undoEntry)
            
            # Write to datastore
            if msg['operation'][1].find('put') == 0:
                key = msg['operation'][2]
                value = msg['operation'][3]
                self._datastore.put_value(key, value) 
            elif msg['operation'][1].find('delete') == 0:
                key = msg['operation'][2]
                self._datastore.delete_key(key)
            else:
                logging.info('R: operation not found. Sending VOTE NO')
                DTEntry = [DTLogMessage.VOTEDNO, str(self.currTransactionIndex)]
                self._DTLog.write(DTEntry)
                return False
         
        # Write to DT log
        DTEntry = [DTLogMessage.VOTEDYES, str(self.currTransactionIndex)]
        self._DTLog.write(DTEntry)
        # Send Vote
        return True
        
    def tpc_commit_replica(self, msg):
        msg = eval(msg)
        logging.info('R: msg: %s', msg)
        # Check trx id
        if self.currTransactionIndex is not None \
            and self.currTransactionIndex == int(msg['operation'][0]):   
            # Commit trx locally
            logging.debug("Writing to REDO log.")
            redoEntry = self._undoLog.peek()
            self._redoLog.write(redoEntry)
            self._datastore.commit()
            
            # Send COMMIT msg to replicas
            DTEntry = [DTLogMessage.COMMIT, str(self.currTransactionIndex)]
            self._DTLog.write(DTEntry)
            
            # Send ACK
            index = self._replicaURIs.index(msg['coordinator'])
            ack_msg = {}
            ack_msg['sender'] = self.server
            ack_msg['state'] = TwoPhaseCommitMessage.ACKNOWLEDGEMENT
            ack_msg['operation'] = redoEntry
            logging.info('R: sending ACK to coordinator.')
            #print 'Replica list: ', self._replicas, 
            #print self._replicas[index].last_sent(), self._replicas[index].last_received()
            #self._replicas[index].service.tpc_ack(str(ack_msg)) 
            logging.info('R: trx is finished.')
            self.tpc_finish()
            return True
        elif self.currTransactionIndex > int(msg['operation'][0]):
            return True
        else:
            logging.info('R: Wrong trx id in msg:')
            return False
        
    def tpc_abort_replica(self, msg):
        msg = eval(msg)
        logging.info('R: msg: %s', msg)
        # Check trx id
        if self.currTransactionIndex is not None \
            and self.currTransactionIndex == int(msg['operation'][0]):   
            # Abort trx locally
            popedEntry = self._undoLog.peek()
            self._undoLog.pop()
            self._datastore.abort()
            
            # Send COMMIT msg to replicas
            DTEntry = [DTLogMessage.ABORT, str(self.currTransactionIndex)]
            self._DTLog.write(DTEntry)
            
            # Send ACK
            index = self._replicaURIs.index(msg['coordinator'])
            ack_msg = {}
            ack_msg['sender'] = self.server
            ack_msg['state'] = TwoPhaseCommitMessage.ACKNOWLEDGEMENT
            ack_msg['operation'] = popedEntry
            #logging.info('R: sending ACK to coordinator')
            #self._replicas[index].service.tpc_ack(str(ack_msg)) 
            logging.info('R: trx is finished')
            self.tpc_finish() 
            return True
        elif self.currTransactionIndex > int(msg['operation'][0]):
            return True
        else:
            logging.info('R: Wrong trx id in msg.')
            return False

    def tpc_ack(self, msg):
        msg = eval(msg)
        logging.info('C: msg: %s', msg)
        if int(msg['operation'][0]) == self.currTransactionIndex \
            and msg['sender'] not in self._acks:
            self._acks.append(msg['sender'])
            
            # check if all ACKs are received
            if len(self._acks) == len(self._replicas):
                self.tpc_finish()
        else:
            logging.info('C: Wrong trx_id or duplicate message.')
    
    def tpc_commit(self):
        logging.info('C: tpc_commit()')
        # Commit trx locally
        logging.debug("writing to REDO log")
        redoEntry = self._undoLog.peek()
        self._redoLog.write(redoEntry)
        self._datastore.commit()
        
        # Send COMMIT msg to replicas
        DTEntry = [DTLogMessage.COMMIT, str(self.currTransactionIndex)]
        self._DTLog.write(DTEntry)
        
        msg = {}
        msg['coordinator'] = self.server
        msg['sender'] = self.server
        msg['state'] = TwoPhaseCommitMessage.COMMIT
        msg['operation'] = redoEntry
        self._acks = []
        logging.info('C: sending COMMIT to %d replicas', len(self._replicas))
        #print "rep: ", self._replicas
        for replica in self._replicas:
            print "Sending  to replica", replica
            #print replica.last_sent()
            #print replica.last_received()
            replica.service.tpc_commit_replica(str(msg))
            print "Sent  to replica"
        logging.info('C: waiting for ACKs.')
        self.tpc_finish()
    
    def tpc_abort(self):
        logging.info('C: tpc_abort()')
        
        # Abort trx locally
        popedEntry = self._undoLog.peek()
        self._undoLog.pop()
        self._datastore.abort()
        
        # Send ROLLBACK msg to replicas
        DTEntry = [DTLogMessage.ABORT, str(self.currTransactionIndex)]
        self._DTLog.write(DTEntry)
        
        msg = {}
        msg['coordinator'] = self.server
        msg['sender'] = self.server
        msg['state'] = TwoPhaseCommitMessage.ROLLBACK
        msg['operation'] = popedEntry
        self._acks = []
        logging.info('C: sending ROLLBACK to replicas')
        for replica in self._replicas:
            replica.service.tpc_abort_replica(str(msg))
        logging.info('C: waiting for ACKs.')
        self.tpc_finish()
        
    def tpc_finish(self):
        # Close existing trx
        logging.info('C: tpc_finish()')
        self._prevTransactionIndex = self.currTransactionIndex
        self.currTransactionIndex = None
    
    def put(self, key, value):
        logging.info('C: put()')
        # Commit Phase 1 
        # Perform Action locally
        status = self.tpc_begin()
        if not status:
            logging.info('C:failure. Another trx already in process.')
            #return False
        else:
            # Write entry into undo log
            logging.debug("writing to UNDO log")
            undoEntry = [str(self.currTransactionIndex), 'put', key, value]
            self._undoLog.write(undoEntry)
            self._datastore.put_value(key, value)
            
        # Send VOTEREQ message to replicas
        DTEntry = [DTLogMessage.START2PC, str(self.currTransactionIndex)]
        self._DTLog.write(DTEntry)
        
        msg = {}
        msg['coordinator'] = self.server
        msg['sender'] = self.server
        msg['state'] = TwoPhaseCommitMessage.VOTEREQ
        msg['operation'] = undoEntry 
        #self._votes = {}
        for replica in self._replicas:
            logging.info('C: send VOTE-REQ to replicas.')
            if replica.service.tpc_vote_replica(str(msg)) == False:
                logging.info('C: got VOTENO, hence aborting the trx.')
                self._datastore.abort()
                self._undoLog.pop()
                self.tpc_abort()
                #return False
                
        # Commit Phase 2
        self.tpc_commit()
        print('\nAdded (%s, %s) to the data-store' % (key, value))
        #return True
          
    def delete(self, key):
        logging.info('C: delete()')
        # Commit Phase 1 
        # Perform Action locally
        status = self.tpc_begin()
        if not status:
            logging.info('C: failure. Another trx already in process.')
            return False 
        else:
            # Write entry into undo log
            logging.debug("writing to UNDO log")
            undoEntry = [str(self.currTransactionIndex), 'delete', key]
            self._undoLog.write(undoEntry)
            self._datastore.delete_key(key)
            
        # Send VOTEREQ message to replicas
        DTEntry = [DTLogMessage.START2PC, str(self.currTransactionIndex)]
        self._DTLog.write(DTEntry)
        
        msg = {}
        msg['coordinator'] = self.server
        msg['sender'] = self.server
        msg['state'] = TwoPhaseCommitMessage.VOTEREQ
        msg['operation'] = undoEntry 
        #self._votes = {}
        for replica in self._replicas:
            if not replica.service.tpc_vote_replica(str(msg)):
                logging.info('C: got VOTENO, hence aborting the trx.')
                self._datastore.abort()
                self._undoLog.pop()
                self.tpc_abort()
                return False
                
        # Commit Phase 2
        self.tpc_commit()
        print('\nAdded (%s) to the data-store' % (key))
        return True
    
    def get(self, key):  
        logging.info('C: get()')
        localValue = self._datastore.get_value(key)
        replicaValue = None
        
        for replica in self._replicas:
            replicaValue = replica.service.get_value_replica(key)
            if replicaValue is not None \
                and localValue is not None \
                and replicaValue!=localValue:
                print 'Inconsistent values!'
                return None
        return localValue
    
    def get_value_replica(self, key):
        logging.info('R: get_value_replica()')
        return self._datastore.get_value(key)
    
    def check_logs(self):
        # check DTLog for last commit
        if (self._DTLog.size() == 0):
            return
        
        last_trx = []
        last_trx = self._DTLog.peek()
        
        # coordinator start-2pc
        if last_trx[0].find(DTLogMessage.START2PC) == 0:
            logging.info("C: node was coordinator in last trx!")

            #check redo logs
            redo_trx = self._redoLog.peek()
            if redo_trx[0] >= last_trx[0]:
                self._rollback(redo_trx)
                return
            
            #check undo-logs
            undo_trx = self._undoLog.peek()
            if undo_trx[0] >= last_trx[0]:
                self.tpc_abort()
                
        # voted YES
        elif last_trx[0].find(DTLogMessage.VOTEDYES) == 0:
            # ask coordinator the result
            pass
        
        # voted NO
        elif last_trx[0].find(DTLogMessage.VOTEDNO) == 0:
            # check undo-logs and rollback
            undo_trx = self._undoLog.peek()
            if undo_trx[0] >= last_trx[0]:
                self.tpc_abort_replica(undo_trx[0])
                
        # committed
        elif last_trx[0].find(DTLogMessage.COMMIT) == 0:
            # send commit msg to replicas
            msg = {}
            msg['coordinator'] = self.server
            msg['sender'] = self.server
            msg['state'] = TwoPhaseCommitMessage.COMMIT
            msg['operation'] = self._redoLog.peek()
            self._acks = []
            logging.info('C: sending ROLLBACK to replicas')
            for replica in self._replicas:
                replica.service.tpc_commit_replica(str(msg))
            logging.info('C: waiting for ACKs.')
            self.tpc_finish()
            
        # aborted
        elif last_trx[0].find(DTLogMessage.ABORT) == 0:
            # send abort message to nodes
            msg = {}
            msg['coordinator'] = self.server
            msg['sender'] = self.server
            msg['state'] = TwoPhaseCommitMessage.ROLLBACK
            msg['operation'] = self._undoLog.peek()
            self._acks = []
            logging.info('C: sending ROLLBACK to replicas')
            for replica in self._replicas:
                replica.service.tpc_abort_replica(str(msg))
            logging.info('C: waiting for ACKs.')
            self.tpc_finish()
    
    def _rollback(self, trx):
        pass