示例#1
0
 def populate(self):
     app = self.app
     pt = app.pt
     uuid = app.uuid
     self.partition_dict = p = {}
     self.replicate_dict = {}
     self.source_dict = {}
     self.ttid_set = set()
     last_tid, last_trans_dict, last_obj_dict, _ = app.dm.getLastIDs()
     next_tid = app.dm.getBackupTID() or last_tid
     next_tid = add64(next_tid, 1) if next_tid else ZERO_TID
     outdated_list = []
     for offset in xrange(pt.getPartitions()):
         for cell in pt.getCellList(offset):
             if cell.getUUID() == uuid and not cell.isCorrupted():
                 self.partition_dict[offset] = p = Partition()
                 if cell.isOutOfDate():
                     outdated_list.append(offset)
                     try:
                         p.next_trans = add64(last_trans_dict[offset], 1)
                     except KeyError:
                         p.next_trans = ZERO_TID
                     p.next_obj = last_obj_dict.get(offset, ZERO_TID)
                     p.max_ttid = INVALID_TID
                 else:
                     p.next_trans = p.next_obj = next_tid
                     p.max_ttid = None
     if outdated_list:
         self.app.master_conn.ask(Packets.AskUnfinishedTransactions(),
                                  offset_list=outdated_list)
示例#2
0
 def populate(self):
     app = self.app
     pt = app.pt
     uuid = app.uuid
     self.partition_dict = p = {}
     self.replicate_dict = {}
     self.source_dict = {}
     self.ttid_set = set()
     last_tid, last_trans_dict, last_obj_dict, _ = app.dm.getLastIDs()
     next_tid = app.dm.getBackupTID() or last_tid
     next_tid = add64(next_tid, 1) if next_tid else ZERO_TID
     outdated_list = []
     for offset in xrange(pt.getPartitions()):
         for cell in pt.getCellList(offset):
             if cell.getUUID() == uuid and not cell.isCorrupted():
                 self.partition_dict[offset] = p = Partition()
                 if cell.isOutOfDate():
                     outdated_list.append(offset)
                     try:
                         p.next_trans = add64(last_trans_dict[offset], 1)
                     except KeyError:
                         p.next_trans = ZERO_TID
                     p.next_obj = last_obj_dict.get(offset, ZERO_TID)
                     p.max_ttid = INVALID_TID
                 else:
                     p.next_trans = p.next_obj = next_tid
                     p.max_ttid = None
     if outdated_list:
         self.app.master_conn.ask(Packets.AskUnfinishedTransactions(),
                                  offset_list=outdated_list)
示例#3
0
 def checkRange(self, conn, *args):
     if self.conn_dict.get(conn, self) != conn.getPeerId():
         # Ignore answers to old requests,
         # because we did nothing to cancel them.
         logging.info("ignored AnswerCheck*Range%r", args)
         return
     self.conn_dict[conn] = args
     answer_set = set(self.conn_dict.itervalues())
     if len(answer_set) > 1:
         for answer in answer_set:
             if type(answer) is not tuple:
                 return
         # TODO: Automatically tell corrupted cells to fix their data
         #       if we know a good source.
         #       For the moment, tell master to put them in CORRUPTED state
         #       and keep up checking if useful.
         uuid = self.app.uuid
         args = None if self.source is None else self.conn_dict[
             None if self.source.getUUID() ==
             uuid else self.source.getConnection()]
         uuid_list = []
         for conn, answer in self.conn_dict.items():
             if answer != args:
                 del self.conn_dict[conn]
                 if conn is None:
                     uuid_list.append(uuid)
                 else:
                     uuid_list.append(conn.getUUID())
                     self.app.closeClient(conn)
         p = Packets.NotifyPartitionCorrupted(self.partition, uuid_list)
         self.app.master_conn.send(p)
         if len(self.conn_dict) <= 1:
             logging.warning("check of partition %u aborted",
                             self.partition)
             self.queue.clear()
             self._nextPartition()
             return
     try:
         count, _, max_tid = args
     except ValueError:  # AnswerCheckSerialRange
         count, _, self.next_tid, _, max_oid = args
         if count < CHECK_COUNT:
             logging.debug("partition %u checked from %s to %s",
                           self.partition, dump(self.min_tid),
                           dump(self.max_tid))
             self._nextPartition()
             return
         self.next_oid = add64(max_oid, 1)
     else:  # AnswerCheckTIDRange
         if count < CHECK_COUNT:
             self.next_tid = self.min_tid
             self.next_oid = ZERO_OID
         else:
             self.next_tid = add64(max_tid, 1)
     self._nextRange()
示例#4
0
文件: checker.py 项目: Nexedi/neoppod
 def checkRange(self, conn, *args):
     if self.conn_dict.get(conn, self) != conn.getPeerId():
         # Ignore answers to old requests,
         # because we did nothing to cancel them.
         logging.info("ignored AnswerCheck*Range%r", args)
         return
     self.conn_dict[conn] = args
     answer_set = set(self.conn_dict.itervalues())
     if len(answer_set) > 1:
         for answer in answer_set:
             if type(answer) is not tuple:
                 return
         # TODO: Automatically tell corrupted cells to fix their data
         #       if we know a good source.
         #       For the moment, tell master to put them in CORRUPTED state
         #       and keep up checking if useful.
         uuid = self.app.uuid
         args = None if self.source is None else self.conn_dict[
             None if self.source.getUUID() == uuid
                  else self.source.getConnection()]
         uuid_list = []
         for conn, answer in self.conn_dict.items():
             if answer != args:
                 del self.conn_dict[conn]
                 if conn is None:
                     uuid_list.append(uuid)
                 else:
                     uuid_list.append(conn.getUUID())
                     self.app.closeClient(conn)
         p = Packets.NotifyPartitionCorrupted(self.partition, uuid_list)
         self.app.master_conn.notify(p)
         if len(self.conn_dict) <= 1:
             logging.warning("check of partition %u aborted", self.partition)
             self.queue.clear()
             self._nextPartition()
             return
     try:
         count, _, max_tid = args
     except ValueError: # AnswerCheckSerialRange
         count, _, self.next_tid, _, max_oid = args
         if count < CHECK_COUNT:
             logging.debug("partition %u checked from %s to %s",
                 self.partition, dump(self.min_tid), dump(self.max_tid))
             self._nextPartition()
             return
         self.next_oid = add64(max_oid, 1)
     else: # AnswerCheckTIDRange
         if count < CHECK_COUNT:
             self.next_tid = self.min_tid
             self.next_oid = ZERO_OID
         else:
             self.next_tid = add64(max_tid, 1)
     self._nextRange()
示例#5
0
 def notifyPartitionChanges(self, cell_list):
     """This is a callback from MasterOperationHandler."""
     abort = False
     added_list = []
     app = self.app
     last_tid, last_trans_dict, last_obj_dict, _ = app.dm.getLastIDs()
     for offset, uuid, state in cell_list:
         if uuid == app.uuid:
             if state in (CellStates.DISCARDED, CellStates.CORRUPTED):
                 try:
                     del self.partition_dict[offset]
                 except KeyError:
                     continue
                 self.replicate_dict.pop(offset, None)
                 self.source_dict.pop(offset, None)
                 abort = abort or self.current_partition == offset
             elif state == CellStates.OUT_OF_DATE:
                 assert offset not in self.partition_dict
                 self.partition_dict[offset] = p = Partition()
                 try:
                     p.next_trans = add64(last_trans_dict[offset], 1)
                 except KeyError:
                     p.next_trans = ZERO_TID
                 p.next_obj = last_obj_dict.get(offset, ZERO_TID)
                 p.max_ttid = INVALID_TID
                 added_list.append(offset)
     if added_list:
         self.app.master_conn.ask(Packets.AskUnfinishedTransactions(),
                                  offset_list=added_list)
     if abort:
         self.abort()
示例#6
0
 def notifyPartitionChanges(self, cell_list):
     """This is a callback from MasterOperationHandler."""
     abort = False
     added_list = []
     app = self.app
     last_tid, last_trans_dict, last_obj_dict, _ = app.dm.getLastIDs()
     for offset, uuid, state in cell_list:
         if uuid == app.uuid:
             if state in (CellStates.DISCARDED, CellStates.CORRUPTED):
                 try:
                     del self.partition_dict[offset]
                 except KeyError:
                     continue
                 self.replicate_dict.pop(offset, None)
                 self.source_dict.pop(offset, None)
                 abort = abort or self.current_partition == offset
             elif state == CellStates.OUT_OF_DATE:
                 assert offset not in self.partition_dict
                 self.partition_dict[offset] = p = Partition()
                 try:
                     p.next_trans = add64(last_trans_dict[offset], 1)
                 except KeyError:
                     p.next_trans = ZERO_TID
                 p.next_obj = last_obj_dict.get(offset, ZERO_TID)
                 p.max_ttid = INVALID_TID
                 added_list.append(offset)
     if added_list:
         self.app.master_conn.ask(Packets.AskUnfinishedTransactions(),
                                  offset_list=added_list)
     if abort:
         self.abort()
示例#7
0
 def getTransaction(self, oid_list):
     self._last_ttid = ttid = add64(self._last_ttid, 1)
     transaction = oid_list, 'user', 'desc', 'ext', False, ttid
     H = "0" * 20
     object_list = [(oid, self.db.holdData(H, '', 1), None)
                    for oid in oid_list]
     return (transaction, object_list)
示例#8
0
 def getTransaction(self, oid_list):
     self._last_ttid = ttid = add64(self._last_ttid, 1)
     transaction = oid_list, 'user', 'desc', 'ext', False, ttid
     H = "0" * 20
     object_list = [(oid, self.db.holdData(H, oid, '', 1), None)
                    for oid in oid_list]
     return (transaction, object_list)
示例#9
0
 def notifyReplicationDone(self, node, offset, tid):
     app = self.app
     cell = app.pt.getCell(offset, node.getUUID())
     tid_list = self.tid_list[offset]
     if tid_list:  # may be empty if the cell is out-of-date
         # or if we're not fully initialized
         if tid < tid_list[0]:
             cell.replicating = tid
         else:
             try:
                 tid = add64(tid_list[bisect(tid_list, tid)], -1)
             except IndexError:
                 last_tid = app.getLastTransaction()
                 if tid < last_tid:
                     tid = last_tid
                     node.send(Packets.Replicate(tid, '', {offset: None}))
     logging.debug("partition %u: updating backup_tid of %r to %s", offset,
                   cell, dump(tid))
     cell.backup_tid = tid
     # TODO: Provide invalidation feedback about new txns to read-only
     #       clients connected to backup cluster. Not only here but also
     #       hooked to in-progress feedback from fetchObjects (storage).
     # Forget tids we won't need anymore.
     cell_list = app.pt.getCellList(offset, readable=True)
     del tid_list[:bisect(tid_list, min(x.backup_tid for x in cell_list))]
     primary_node = self.primary_partition_dict.get(offset)
     primary = primary_node is node
     result = None if primary else app.pt.setUpToDate(node, offset)
     assert cell.isReadable()
     if result:  # was out-of-date
         if primary_node is not None:
             max_tid, = [
                 x.backup_tid for x in cell_list
                 if x.getNode() is primary_node
             ]
             if tid < max_tid:
                 cell.replicating = max_tid
                 logging.debug(
                     "ask %s to replicate partition %u up to %s from %s",
                     uuid_str(node.getUUID()), offset, dump(max_tid),
                     uuid_str(primary_node.getUUID()))
                 node.send(
                     Packets.Replicate(max_tid, '',
                                       {offset: primary_node.getAddress()}))
     else:
         if app.getClusterState() == ClusterStates.BACKINGUP:
             self.triggerBackup(node)
         if primary:
             # Notify secondary storages that they can replicate from
             # primary ones, even if they are already replicating.
             p = Packets.Replicate(tid, '', {offset: node.getAddress()})
             for cell in cell_list:
                 if max(cell.backup_tid, cell.replicating) < tid:
                     cell.replicating = tid
                     logging.debug(
                         "ask %s to replicate partition %u up to %s from %s",
                         uuid_str(cell.getUUID()), offset, dump(tid),
                         uuid_str(node.getUUID()))
                     cell.getNode().send(p)
     return result
示例#10
0
    def askObject(self, conn, oid, serial, tid):
        backup_tid = self.app.dm.getBackupTID()
        if serial:
            if serial > backup_tid:
                # obj lookup will find nothing, but return properly either
                # OidDoesNotExist or OidNotFound
                serial = ZERO_TID
        elif tid:
            tid = min(tid, add64(backup_tid, 1))

        # limit "latest obj" query to tid <= backup_tid
        else:
            tid = add64(backup_tid, 1)

        super(ClientReadOnlyOperationHandler,
              self).askObject(conn, oid, serial, tid)
示例#11
0
 def getBackupTID(self):
     outdated_set = set(self.app.pt.getOutdatedOffsetListFor(self.app.uuid))
     tid = INVALID_TID
     for offset, p in self.partition_dict.iteritems():
         if offset not in outdated_set:
             tid = min(tid, p.next_trans, p.next_obj)
     if ZERO_TID != tid != INVALID_TID:
         return add64(tid, -1)
     return ZERO_TID
示例#12
0
 def getBackupTID(self):
     outdated_set = set(self.app.pt.getOutdatedOffsetListFor(self.app.uuid))
     tid = INVALID_TID
     for offset, p in self.partition_dict.iteritems():
         if offset not in outdated_set:
             tid = min(tid, p.next_trans, p.next_obj)
     if ZERO_TID != tid != INVALID_TID:
         return add64(tid, -1)
     return ZERO_TID
示例#13
0
 def _():
     # Unfortunately, copyTransactionsFrom does not abort in case
     # of failure, so we have to reopen.
     zodb = storageFromString(self._storage)
     try:
         self.min_tid = util.add64(zodb.lastTransaction(), 1)
         zodb.copyTransactionsFrom(self)
     finally:
         zodb.close()
示例#14
0
    def testResumingReplication(self, cluster):
        """
        Check from where replication resumes for an OUT_OF_DATE cell that has
        a hole, which is possible because OUT_OF_DATE cells are writable.
        """
        ask = []

        def logReplication(conn, packet):
            if isinstance(
                    packet,
                (Packets.AskFetchTransactions, Packets.AskFetchObjects)):
                ask.append(packet._args[2:])

        def getTIDList():
            return [t.tid for t in c.db().storage.iterator()]

        s0, s1 = cluster.storage_list
        t, c = cluster.getTransaction()
        r = c.root()
        # s1 is UP_TO_DATE and it has the initial transaction.
        # Let's outdate it: replication will have to resume just after this
        # transaction, regardless of future written transactions.
        # To make sure, we get a hole in the cell, we block replication.
        s1.stop()
        cluster.join((s1, ))
        r._p_changed = 1
        t.commit()
        s1.resetNode()
        with Patch(replicator.Replicator, connected=lambda *_: None):
            s1.start()
            self.tic()
            r._p_changed = 1
            t.commit()
            self.tic()
            s1.stop()
            cluster.join((s1, ))
        tids = getTIDList()
        s1.resetNode()
        # Initialization done. Now we check that replication is correct
        # and efficient.
        with ConnectionFilter() as f:
            f.add(logReplication)
            s1.start()
            self.tic()
        self.assertEqual([], cluster.getOutdatedCells())
        s0.stop()
        cluster.join((s0, ))
        self.assertEqual(tids, getTIDList())
        t0_next = add64(tids[0], 1)
        self.assertEqual(ask, [
            (t0_next, tids[2], tids[2:]),
            (t0_next, tids[2], ZERO_OID, {
                tids[2]: [ZERO_OID]
            }),
        ])
示例#15
0
 def notifyReplicationDone(self, node, offset, tid):
     app = self.app
     cell = app.pt.getCell(offset, node.getUUID())
     tid_list = self.tid_list[offset]
     if tid_list: # may be empty if the cell is out-of-date
                  # or if we're not fully initialized
         if tid < tid_list[0]:
             cell.replicating = tid
         else:
             try:
                 tid = add64(tid_list[bisect(tid_list, tid)], -1)
             except IndexError:
                 last_tid = app.getLastTransaction()
                 if tid < last_tid:
                     tid = last_tid
                     node.notify(Packets.Replicate(tid, '', {offset: None}))
     logging.debug("partition %u: updating backup_tid of %r to %s",
                   offset, cell, dump(tid))
     cell.backup_tid = tid
     # Forget tids we won't need anymore.
     cell_list = app.pt.getCellList(offset, readable=True)
     del tid_list[:bisect(tid_list, min(x.backup_tid for x in cell_list))]
     primary_node = self.primary_partition_dict.get(offset)
     primary = primary_node is node
     result = None if primary else app.pt.setUpToDate(node, offset)
     assert cell.isReadable()
     if result: # was out-of-date
         if primary_node is not None:
             max_tid, = [x.backup_tid for x in cell_list
                                      if x.getNode() is primary_node]
             if tid < max_tid:
                 cell.replicating = max_tid
                 logging.debug(
                     "ask %s to replicate partition %u up to %s from %s",
                     uuid_str(node.getUUID()), offset,  dump(max_tid),
                     uuid_str(primary_node.getUUID()))
                 node.notify(Packets.Replicate(max_tid, '',
                     {offset: primary_node.getAddress()}))
     else:
         if app.getClusterState() == ClusterStates.BACKINGUP:
             self.triggerBackup(node)
         if primary:
             # Notify secondary storages that they can replicate from
             # primary ones, even if they are already replicating.
             p = Packets.Replicate(tid, '', {offset: node.getAddress()})
             for cell in cell_list:
                 if max(cell.backup_tid, cell.replicating) < tid:
                     cell.replicating = tid
                     logging.debug(
                         "ask %s to replicate partition %u up to %s from %s",
                         uuid_str(cell.getUUID()), offset,
                         dump(tid), uuid_str(node.getUUID()))
                     cell.getNode().notify(p)
     return result
示例#16
0
def iterator(app, start=None, stop=None):
    """NEO transaction iterator"""
    if start is None:
        start = ZERO_TID
    stop = min(stop or MAX_TID, app.last_tid)
    while 1:
        max_tid, chunk = app.transactionLog(start, stop, CHUNK_LENGTH)
        if not chunk:
            break  # nothing more
        for txn in chunk:
            yield Transaction(app, txn)
        start = add64(max_tid, 1)
示例#17
0
文件: test.py 项目: Nexedi/neoppod
 def testInvalidTTID(self):
     cluster = NEOCluster()
     try:
         cluster.start()
         client = cluster.client
         txn = transaction.Transaction()
         client.tpc_begin(txn)
         txn_context = client._txn_container.get(txn)
         txn_context["ttid"] = add64(txn_context["ttid"], 1)
         self.assertRaises(POSException.StorageError, client.tpc_finish, txn, None)
     finally:
         cluster.stop()
示例#18
0
def iterator(app, start=None, stop=None):
    """NEO transaction iterator"""
    if start is None:
        start = ZERO_TID
    stop = min(stop or MAX_TID, app.last_tid)
    while 1:
        max_tid, chunk = app.transactionLog(start, stop, CHUNK_LENGTH)
        if not chunk:
            break # nothing more
        for txn in chunk:
            yield Transaction(app, txn)
        start = add64(max_tid, 1)
示例#19
0
 def iterator(self):
     db = self._db
     np = self._np
     offset_list = xrange(np)
     while 1:
         with db:
             # Check the partition table at the beginning of every
             # transaction. Once the import is finished and at least one
             # cell is replicated, it is possible that some of this node
             # get outdated. In this case, wait for the next PT change.
             if np == len(db._readable_set):
                 while 1:
                     tid_list = []
                     max_tid = MAX_TID
                     for offset in offset_list:
                         x = db.getReplicationTIDList(
                             self.min_tid, max_tid, self.chunk_size, offset)
                         tid_list += x
                         if len(x) == self.chunk_size:
                             max_tid = x[-1]
                     if not tid_list:
                         break
                     tid_list.sort()
                     for tid in tid_list:
                         if self._stop.is_set():
                             return
                         yield TransactionRecord(db, tid)
                         if tid == max_tid:
                             break
                     else:
                         self.min_tid = util.add64(tid, 1)
                         break
                     self.min_tid = util.add64(tid, 1)
         if not self._event.is_set():
             self._idle.set()
             self._event.wait()
             self._idle.clear()
         self._event.clear()
         if self._stop.is_set():
             break
示例#20
0
 def testInvalidTTID(self):
     cluster = NEOCluster()
     try:
         cluster.start()
         client = cluster.client
         txn = transaction.Transaction()
         client.tpc_begin(txn)
         txn_context = client._txn_container.get(txn)
         txn_context['ttid'] = add64(txn_context['ttid'], 1)
         self.assertRaises(POSException.StorageError,
             client.tpc_finish, txn, None, lambda tid: None)
     finally:
         cluster.stop()
示例#21
0
 def finish(self):
     offset = self.current_partition
     tid = self.replicate_tid
     del self.current_partition, self.replicate_tid
     p = self.partition_dict[offset]
     p.next_obj = add64(tid, 1)
     self.updateBackupTID()
     if not p.max_ttid:
         p = Packets.NotifyReplicationDone(offset, tid)
         self.app.master_conn.notify(p)
     logging.debug("partition %u replicated up to %s from %r",
                   offset, dump(tid), self.current_node)
     self.getCurrentConnection().setReconnectionNoDelay()
     self._nextPartition()
示例#22
0
 def finish(self):
     offset = self.current_partition
     tid = self.replicate_tid
     del self.current_partition, self.replicate_tid
     p = self.partition_dict[offset]
     p.next_obj = add64(tid, 1)
     self.updateBackupTID()
     if not p.max_ttid:
         p = Packets.NotifyReplicationDone(offset, tid)
         self.app.master_conn.notify(p)
     logging.debug("partition %u replicated up to %s from %r", offset,
                   dump(tid), self.current_node)
     self.getCurrentConnection().setReconnectionNoDelay()
     self._nextPartition()
示例#23
0
 def fetchObjects(self, min_tid=None, min_oid=ZERO_OID):
     offset = self.current_partition
     p = self.partition_dict[offset]
     max_tid = self.replicate_tid
     dm = self.app.dm
     if min_tid:
         p.next_obj = min_tid
         self.updateBackupTID()
         dm.updateCellTID(offset, add64(min_tid, -1))
         dm.commit()  # like in fetchTransactions
     else:
         min_tid = p.next_obj
         p.next_trans = add64(max_tid, 1)
     object_dict = {}
     for serial, oid in dm.getReplicationObjectList(min_tid, max_tid,
                                                    FETCH_COUNT, offset,
                                                    min_oid):
         try:
             object_dict[serial].append(oid)
         except KeyError:
             object_dict[serial] = [oid]
     self._conn_msg_id = self.current_node.ask(
         Packets.AskFetchObjects(offset, FETCH_COUNT, min_tid, max_tid,
                                 min_oid, object_dict))
示例#24
0
 def finish(self):
     offset = self.current_partition
     tid = self.replicate_tid
     del self.current_partition, self._conn_msg_id, self.replicate_tid
     p = self.partition_dict[offset]
     p.next_obj = add64(tid, 1)
     self.updateBackupTID()
     if p.max_ttid or offset in self.replicate_dict and \
                      offset not in self.source_dict:
         logging.debug("unfinished transactions: %r", self.ttid_set)
     else:
         self.app.tm.replicated(offset, tid)
     logging.debug("partition %u replicated up to %s from %r", offset,
                   dump(tid), self.current_node)
     self.getCurrentConnection().setReconnectionNoDelay()
     self._nextPartition()
示例#25
0
 def fetchObjects(self, min_tid=None, min_oid=ZERO_OID):
     offset = self.current_partition
     p = self.partition_dict[offset]
     max_tid = self.replicate_tid
     if min_tid:
         p.next_obj = min_tid
     else:
         min_tid = p.next_obj
         p.next_trans = add64(max_tid, 1)
     object_dict = {}
     for serial, oid in self.app.dm.getReplicationObjectList(min_tid,
             max_tid, FETCH_COUNT, offset, min_oid):
         try:
             object_dict[serial].append(oid)
         except KeyError:
             object_dict[serial] = [oid]
     self.current_node.getConnection().ask(Packets.AskFetchObjects(
         offset, FETCH_COUNT, min_tid, max_tid, min_oid, object_dict))
示例#26
0
 def backup(self, tid, source_dict):
     next_tid = None
     for offset, source in source_dict.iteritems():
         if source:
             self.source_dict[offset] = source
             self.replicate_dict[offset] = tid
         elif offset != self.current_partition and \
              offset not in self.replicate_dict:
             # The master did its best to avoid useless replication orders
             # but there may still be a few, and we may receive redundant
             # update notification of backup_tid.
             # So, we do nothing here if we are already replicating.
             p = self.partition_dict[offset]
             if not next_tid:
                 next_tid = add64(tid, 1)
             p.next_trans = p.next_obj = next_tid
     if next_tid:
         self.updateBackupTID()
     self._nextPartition()
示例#27
0
 def backup(self, tid, source_dict):
     next_tid = None
     for offset, source in source_dict.iteritems():
         if source:
             self.source_dict[offset] = source
             self.replicate_dict[offset] = tid
         elif offset != self.current_partition and \
              offset not in self.replicate_dict:
             # The master did its best to avoid useless replication orders
             # but there may still be a few, and we may receive redundant
             # update notification of backup_tid.
             # So, we do nothing here if we are already replicating.
             p = self.partition_dict[offset]
             if not next_tid:
                 next_tid = add64(tid, 1)
             p.next_trans = p.next_obj = next_tid
     if next_tid:
         self.updateBackupTID()
     self._nextPartition()
示例#28
0
 def fetchObjects(self, min_tid=None, min_oid=ZERO_OID):
     offset = self.current_partition
     p = self.partition_dict[offset]
     max_tid = self.replicate_tid
     if min_tid:
         p.next_obj = min_tid
     else:
         min_tid = p.next_obj
         p.next_trans = add64(max_tid, 1)
     object_dict = {}
     for serial, oid in self.app.dm.getReplicationObjectList(
             min_tid, max_tid, FETCH_COUNT, offset, min_oid):
         try:
             object_dict[serial].append(oid)
         except KeyError:
             object_dict[serial] = [oid]
     self.current_node.getConnection().ask(
         Packets.AskFetchObjects(offset, FETCH_COUNT, min_tid, max_tid,
                                 min_oid, object_dict))
示例#29
0
 def notifyPartitionChanges(self, cell_list):
     """This is a callback from MasterOperationHandler."""
     abort = False
     added_list = []
     discarded_list = []
     readable_list = []
     app = self.app
     last_tid, last_trans_dict, last_obj_dict, _ = app.dm.getLastIDs()
     for offset, uuid, state in cell_list:
         if uuid == app.uuid:
             if state in (CellStates.DISCARDED, CellStates.CORRUPTED):
                 try:
                     del self.partition_dict[offset]
                 except KeyError:
                     continue
                 self.replicate_dict.pop(offset, None)
                 self.source_dict.pop(offset, None)
                 abort = abort or self.current_partition == offset
                 discarded_list.append(offset)
             elif state == CellStates.OUT_OF_DATE:
                 assert offset not in self.partition_dict
                 self.partition_dict[offset] = p = Partition()
                 try:
                     p.next_trans = add64(last_trans_dict[offset], 1)
                 except KeyError:
                     p.next_trans = ZERO_TID
                 p.next_obj = last_obj_dict.get(offset, ZERO_TID)
                 p.max_ttid = INVALID_TID
                 added_list.append(offset)
             else:
                 assert state in (CellStates.UP_TO_DATE,
                                  CellStates.FEEDING), state
                 readable_list.append(offset)
     tm = app.tm
     if added_list:
         tm.replicating(added_list)
     if discarded_list:
         tm.discarded(discarded_list)
     if readable_list:
         tm.readable(readable_list)
     if abort:
         self.abort()
示例#30
0
 def invalidatePartitions(self, tid, prev_tid, partition_set):
     app = self.app
     app.setLastTransaction(tid)
     pt = app.pt
     trigger_set = set()
     untouched_dict = defaultdict(dict)
     for offset in xrange(pt.getPartitions()):
         try:
             last_max_tid = self.tid_list[offset][-1]
         except IndexError:
             last_max_tid = prev_tid
         if offset in partition_set:
             primary_list = []
             node_list = []
             cell_list = pt.getCellList(offset, readable=True)
             for cell in cell_list:
                 node = cell.getNode()
                 assert node.isConnected(), node
                 if cell.backup_tid == prev_tid:
                     if prev_tid == tid:
                         # Connecting to upstream: any node is that is
                         # up-to-date wrt upstream is candidate for being
                         # primary.
                         assert self.ignore_invalidations
                         if app.isStorageReady(node.getUUID()):
                             primary_list.append(node)
                         continue
                     # Let's given 4 TID t0,t1,t2,t3: if a cell is only
                     # modified by t0 & t3 and has all data for t0, 4 values
                     # are possible for its 'backup_tid' until it replicates
                     # up to t3: t0, t1, t2 or t3 - 1
                     # Choosing the smallest one (t0) is easier to implement
                     # but when leaving backup mode, we would always lose
                     # data if the last full transaction does not modify
                     # all partitions. t1 is wrong for the same reason.
                     # So we have chosen the highest one (t3 - 1).
                     # t2 should also work but maybe harder to implement.
                     cell.backup_tid = add64(tid, -1)
                     logging.debug(
                         "partition %u: updating backup_tid of %r to %s",
                         offset, cell, dump(cell.backup_tid))
                 else:
                     assert cell.backup_tid < last_max_tid, (
                         cell.backup_tid, last_max_tid, prev_tid, tid)
                 if app.isStorageReady(node.getUUID()):
                     node_list.append(node)
             # Make sure we have a primary storage for this partition.
             if offset not in self.primary_partition_dict:
                 self.primary_partition_dict[offset] = \
                     random.choice(primary_list or node_list)
             if node_list:
                 self.tid_list[offset].append(tid)
                 if primary_list:
                     # Resume replication to secondary cells.
                     self._triggerSecondary(
                         self.primary_partition_dict[offset], offset, tid,
                         cell_list)
                 else:
                     trigger_set.update(node_list)
         else:
             # Partition not touched, so increase 'backup_tid' of all
             # "up-to-date" replicas, without having to replicate.
             for cell in pt.getCellList(offset, readable=True):
                 if last_max_tid <= cell.backup_tid:
                     cell.backup_tid = tid
                     untouched_dict[cell.getNode()][offset] = None
                 elif last_max_tid <= cell.replicating:
                     # Same for 'replicating' to avoid useless orders.
                     logging.debug(
                         "silently update replicating order"
                         " of %s for partition %u, up to %s",
                         uuid_str(cell.getUUID()), offset, dump(tid))
                     cell.replicating = tid
     for node, untouched_dict in untouched_dict.iteritems():
         if app.isStorageReady(node.getUUID()):
             node.send(Packets.Replicate(tid, '', untouched_dict))
     for node in trigger_set:
         self.triggerBackup(node)
     count = sum(map(len, self.tid_list))
     if self.debug_tid_count < count:
         logging.debug("Maximum number of tracked tids: %u", count)
         self.debug_tid_count = count
示例#31
0
 def invalidatePartitions(self, tid, partition_set):
     app = self.app
     prev_tid = app.getLastTransaction()
     app.setLastTransaction(tid)
     pt = app.pt
     trigger_set = set()
     untouched_dict = defaultdict(dict)
     for offset in xrange(pt.getPartitions()):
         try:
             last_max_tid = self.tid_list[offset][-1]
         except IndexError:
             last_max_tid = prev_tid
         if offset in partition_set:
             self.tid_list[offset].append(tid)
             node_list = []
             for cell in pt.getCellList(offset, readable=True):
                 node = cell.getNode()
                 assert node.isConnected(), node
                 if cell.backup_tid == prev_tid:
                     # Let's given 4 TID t0,t1,t2,t3: if a cell is only
                     # modified by t0 & t3 and has all data for t0, 4 values
                     # are possible for its 'backup_tid' until it replicates
                     # up to t3: t0, t1, t2 or t3 - 1
                     # Choosing the smallest one (t0) is easier to implement
                     # but when leaving backup mode, we would always lose
                     # data if the last full transaction does not modify
                     # all partitions. t1 is wrong for the same reason.
                     # So we have chosen the highest one (t3 - 1).
                     # t2 should also work but maybe harder to implement.
                     cell.backup_tid = add64(tid, -1)
                     logging.debug(
                         "partition %u: updating backup_tid of %r to %s",
                         offset, cell, dump(cell.backup_tid))
                 else:
                     assert cell.backup_tid < last_max_tid, (
                         cell.backup_tid, last_max_tid, prev_tid, tid)
                 if app.isStorageReady(node.getUUID()):
                     node_list.append(node)
             assert node_list
             trigger_set.update(node_list)
             # Make sure we have a primary storage for this partition.
             if offset not in self.primary_partition_dict:
                 self.primary_partition_dict[offset] = \
                     random.choice(node_list)
         else:
             # Partition not touched, so increase 'backup_tid' of all
             # "up-to-date" replicas, without having to replicate.
             for cell in pt.getCellList(offset, readable=True):
                 if last_max_tid <= cell.backup_tid:
                     cell.backup_tid = tid
                     untouched_dict[cell.getNode()][offset] = None
                 elif last_max_tid <= cell.replicating:
                     # Same for 'replicating' to avoid useless orders.
                     logging.debug("silently update replicating order"
                         " of %s for partition %u, up to %s",
                         uuid_str(cell.getUUID()), offset,  dump(tid))
                     cell.replicating = tid
     for node, untouched_dict in untouched_dict.iteritems():
         if app.isStorageReady(node.getUUID()):
             node.notify(Packets.Replicate(tid, '', untouched_dict))
     for node in trigger_set:
         self.triggerBackup(node)
     count = sum(map(len, self.tid_list))
     if self.debug_tid_count < count:
         logging.debug("Maximum number of tracked tids: %u", count)
         self.debug_tid_count = count
示例#32
0
    def testResumingBackupReplication(self, backup):
        upstream = backup.upstream
        t, c = upstream.getTransaction()
        r = c.root()
        r[1] = PCounter()
        t.commit()
        r[2] = ob = PCounter()
        tids = []

        def newTransaction():
            r._p_changed = ob._p_changed = 1
            with upstream.moduloTID(0):
                t.commit()
            self.tic()
            tids.append(r._p_serial)

        def getTIDList(storage):
            return storage.dm.getReplicationTIDList(tids[0], MAX_TID, 9, 0)

        newTransaction()
        self.assertEqual(u64(ob._p_oid), 2)
        getBackupTid = backup.master.pt.getBackupTid

        # Check when an OUT_OF_DATE cell has more data than an UP_TO_DATE one.
        primary = backup.master.backup_app.primary_partition_dict[0]._uuid
        slave, primary = sorted(backup.storage_list,
                                key=lambda x: x.uuid == primary)
        with ConnectionFilter() as f:

            @f.delayAnswerFetchTransactions
            def delay(conn, x={None: 0, primary.uuid: 0}):
                return x.pop(conn.getUUID(), 1)

            newTransaction()
            self.assertEqual(getBackupTid(), tids[1])
            primary.stop()
            backup.join((primary, ))
            primary.resetNode()
            primary.start()
            self.tic()
            primary, slave = slave, primary
            self.assertEqual(tids, getTIDList(slave))
            self.assertEqual(tids[:1], getTIDList(primary))
            self.assertEqual(getBackupTid(), add64(tids[1], -1))
            self.assertEqual(f.filtered_count, 3)
        self.tic()
        self.assertEqual(4, self.checkBackup(backup))
        self.assertEqual(getBackupTid(min), tids[1])

        # Check that replication resumes from the maximum possible tid
        # (for UP_TO_DATE cells of a backup cluster). More precisely:
        # - cells are handled independently (done here by blocking replication
        #   of partition 1 to keep the backup TID low)
        # - trans and obj are also handled independently (with FETCH_COUNT=1,
        #   we interrupt replication of obj in the middle of a transaction)
        slave.stop()
        backup.join((slave, ))
        ask = []

        def delayReplicate(conn, packet):
            if isinstance(packet, Packets.AskFetchObjects):
                if len(ask) == 6:
                    return True
            elif not isinstance(packet, Packets.AskFetchTransactions):
                return
            ask.append(packet._args)

        conn, = upstream.master.getConnectionList(backup.master)
        with ConnectionFilter() as f, Patch(
                replicator.Replicator,
                _nextPartitionSortKey=lambda orig, self, offset: offset):
            f.add(delayReplicate)
            delayReconnect = f.delayAskLastTransaction()
            conn.close()
            newTransaction()
            newTransaction()
            newTransaction()
            self.assertFalse(ask)
            self.assertEqual(f.filtered_count, 1)
            with Patch(replicator, FETCH_COUNT=1):
                f.remove(delayReconnect)
                self.tic()
            t1_next = add64(tids[1], 1)
            self.assertEqual(
                ask,
                [
                    # trans
                    (0, 1, t1_next, tids[4], []),
                    (0, 1, tids[3], tids[4], []),
                    (0, 1, tids[4], tids[4], []),
                    # obj
                    (0, 1, t1_next, tids[4], ZERO_OID, {}),
                    (0, 1, tids[2], tids[4], p64(2), {}),
                    (0, 1, tids[3], tids[4], ZERO_OID, {}),
                ])
            del ask[:]
            max_ask = None
            backup.stop()
            newTransaction()
            backup.start((primary, ))
            n = replicator.FETCH_COUNT
            t4_next = add64(tids[4], 1)
            self.assertEqual(ask, [
                (0, n, t4_next, tids[5], []),
                (0, n, tids[3], tids[5], ZERO_OID, {
                    tids[3]: [ZERO_OID]
                }),
                (1, n, t1_next, tids[5], []),
                (1, n, t1_next, tids[5], ZERO_OID, {}),
            ])
        self.tic()
        self.assertEqual(2, self.checkBackup(backup))