コード例 #1
0
class test_bug010(wttest.WiredTigerTestCase):
    name = 'test_bug010'
    uri = 'table:' + name
    num_tables = 2000 if wttest.islongtest() else 200

    # Disable checkpoint sync, to make checkpoints faster and
    # increase the likelihood of triggering the symptom
    conn_config = 'checkpoint_sync=false'

    def test_checkpoint_dirty(self):
        # Create a lot of tables
        # insert the same item in each
        # Start a checkpoint with some of the updates
        # Create another checkpoint that should contain all data consistently
        # Read from the checkpoint and make sure the data is consistent
        for i in range(0, self.num_tables):
            self.printVerbose(3, 'Creating table ' + str(i))
            self.session.create(self.uri + str(i),
                                'key_format=S,value_format=i')
            c = self.session.open_cursor(self.uri + str(i), None)
            c['a'] = 0
            c.close()

        self.session.checkpoint()

        iterations = 1
        expected_val = 0
        for its in range(1, 10):
            self.printVerbose(3, 'Doing iteration ' + str(its))

            # Create a checkpoint thread
            done = threading.Event()
            ckpt = wtthread.checkpoint_thread(self.conn, done)
            ckpt.start()
            try:
                expected_val += 1
                for i in range(0, self.num_tables):
                    c = self.session.open_cursor(self.uri + str(i), None)
                    c['a'] = expected_val
                    c.close()
            finally:
                done.set()
                ckpt.join()

            # Execute another checkpoint, to make sure we have a consistent
            # view of the data.
            self.session.checkpoint()
            for i in range(0, self.num_tables):
                c = self.session.open_cursor(
                    self.uri + str(i), None, 'checkpoint=WiredTigerCheckpoint')
                c.next()
                self.assertEquals(c.get_value(), expected_val,
                    msg='Mismatch on iteration ' + str(its) +\
                                        ' for table ' + str(i))
                c.close()
コード例 #2
0
class test_backup02(wttest.WiredTigerTestCase):
    uri = 'table:test_backup02'
    fmt = 'L'
    dsize = 100
    nops = 200
    nthreads = 1
    time = 60 if wttest.islongtest() else 10

    def test_backup02(self):
        done = threading.Event()
        uris = list()
        uris.append(self.uri + str(1))
        uris.append(self.uri + str(2))
        uris.append(self.uri + str(3))
        for this_uri in uris:
            self.session.create(this_uri,
                                "key_format=" + self.fmt + ",value_format=S")
        # TODO: Ideally we'd like a checkpoint thread, separate to the backup
        # thread. Need a way to stop checkpoints while doing backups.


#        ckpt = checkpoint_thread(self.conn, done)
#        ckpt.start()
        bkp = backup_thread(self.conn, 'backup.dir', done)
        bkp.start()

        queue = Queue.Queue()
        my_data = 'a' * self.dsize
        for i in xrange(self.nops):
            queue.put_nowait(('gi', i, my_data))

        opthreads = []
        for i in xrange(self.nthreads):
            t = op_thread(self.conn, uris, self.fmt, queue, done)
            opthreads.append(t)
            t.start()

        # Add 200 update entries into the queue every .1 seconds.
        more_time = self.time
        while more_time > 0:
            time.sleep(0.1)
            my_data = str(more_time) + 'a' * (self.dsize - len(str(more_time)))
            more_time = more_time - 0.1
            for i in xrange(self.nops):
                queue.put_nowait(('gu', i, my_data))

        queue.join()
        done.set()
        #        # Wait for checkpoint thread to notice status change.
        #        ckpt.join()
        for t in opthreads:
            t.join()
        bkp.join()
コード例 #3
0
class test_intpack(wttest.WiredTigerTestCase):
    name = 'test_intpack'

    # It's useful to test a larger range but avoid the CPU overhead normally
    base_range = 66000 if wttest.islongtest() else 5000

    # We have to be a bit verbose here with naming, scenario names are
    # case insensitive and must be unique.

    scenarios = make_scenarios([
        ('int8_t_b', dict(formatcode='b', low=-128, high=127, nbits=8)),
        ('uint8_t_B', dict(formatcode='B', low=0, high=255, nbits=8)),
        ('fix_len_8t', dict(formatcode='8t', low=0, high=255, nbits=8)),
        ('fix_len_5t', dict(formatcode='5t', low=0, high=31, nbits=5)),
        ('int16_t_h', dict(formatcode='h', low=-32768, high=32767, nbits=16)),
        ('uint16_t_H', dict(formatcode='H', low=0, high=65535, nbits=16)),
        ('int32_t_i',
         dict(formatcode='i', low=-2147483648, high=2147483647, nbits=32)),
        ('uint32_t_I', dict(formatcode='I', low=0, high=4294967295, nbits=32)),
        ('int32_t_l',
         dict(formatcode='l', low=-2147483648, high=2147483647, nbits=32)),
        ('uint32_t_L', dict(formatcode='L', low=0, high=4294967295, nbits=32)),
        ('int64_t_q',
         dict(formatcode='q',
              low=-9223372036854775808,
              high=9223372036854775807,
              nbits=64)),
        ('uint64_t_Q',
         dict(formatcode='Q', low=0, high=18446744073709551615, nbits=64)),
    ])

    def test_packing(self):
        pt = PackTester(self.formatcode, self.low, self.high,
                        self.assertEquals)
        self.assertEquals(2**self.nbits, self.high - self.low + 1)
        pt.initialize(self.session)
        pt.check_range(-self.base_range, self.base_range)
        if self.nbits >= 32:
            e32 = 2**32
            pt.check_range(e32 - 1000, e32 + 1000)
            pt.check_range(-e32 - 1000, -e32 + 1000)
        if self.nbits >= 64:
            e64 = 2**64
            pt.check_range(e64 - 1000, e64 + 1000)
            pt.check_range(-e64 - 1000, -e64 + 1000)
            pt.truncate()
            i = 8
            while i < 1 << 60:
                pt.check_range(-i - 1, -i + 1)
                pt.check_range(i - 1, i + 1)
                i <<= 1
コード例 #4
0
ファイル: test_txn02.py プロジェクト: stamhe/wiredtiger
class test_txn02(wttest.WiredTigerTestCase, suite_subprocess):
    logmax = "100K"
    tablename = 'test_txn02'
    uri = 'table:' + tablename
    archive_list = ['true', 'false']
    conn_list = ['reopen', 'stay_open']
    sync_list = [
        '(method=dsync,enabled)', '(method=fsync,enabled)',
        '(method=none,enabled)', '(enabled=false)'
    ]

    types = [
        ('row',
         dict(tabletype='row', create_params='key_format=i,value_format=i')),
        ('var',
         dict(tabletype='var', create_params='key_format=r,value_format=i')),
        ('fix',
         dict(tabletype='fix', create_params='key_format=r,value_format=8t')),
    ]
    op1s = [
        ('i4', dict(op1=('insert', 4))),
        ('r1', dict(op1=('remove', 1))),
        ('u10', dict(op1=('update', 10))),
    ]
    op2s = [
        ('i6', dict(op2=('insert', 6))),
        ('r4', dict(op2=('remove', 4))),
        ('u4', dict(op2=('update', 4))),
    ]
    op3s = [
        ('i12', dict(op3=('insert', 12))),
        ('r4', dict(op3=('remove', 4))),
        ('u4', dict(op3=('update', 4))),
    ]
    op4s = [
        ('i14', dict(op4=('insert', 14))),
        ('r12', dict(op4=('remove', 12))),
        ('u12', dict(op4=('update', 12))),
    ]
    txn1s = [('t1c', dict(txn1='commit')), ('t1r', dict(txn1='rollback'))]
    txn2s = [('t2c', dict(txn2='commit')), ('t2r', dict(txn2='rollback'))]
    txn3s = [('t3c', dict(txn3='commit')), ('t3r', dict(txn3='rollback'))]
    txn4s = [('t4c', dict(txn4='commit')), ('t4r', dict(txn4='rollback'))]

    all_scenarios = multiply_scenarios('.', types, op1s, txn1s, op2s, txn2s,
                                       op3s, txn3s, op4s, txn4s)

    # This test generates thousands of potential scenarios.
    # For default runs, we'll use a small subset of them, for
    # long runs (when --long is set) we'll set a much larger limit.
    scenarios = number_scenarios(prune_scenarios(all_scenarios, 20, 5000))

    # Each check_log() call takes a second, so we don't call it for
    # every scenario, we'll limit it to the value of checklog_calls.
    checklog_calls = 100 if wttest.islongtest() else 2
    checklog_mod = (len(scenarios) / checklog_calls + 1)

    # scenarios = number_scenarios(multiply_scenarios('.', types,
    # op1s, txn1s, op2s, txn2s, op3s, txn3s, op4s, txn4s)) [:3]
    # Overrides WiredTigerTestCase
    def setUpConnectionOpen(self, dir):
        self.home = dir
        # Cycle through the different transaction_sync values in a
        # deterministic manner.
        self.txn_sync = self.sync_list[self.scenario_number %
                                       len(self.sync_list)]
        #
        # We don't want to run zero fill with only the same settings, such
        # as archive or sync, which are an even number of options.
        #
        freq = 3
        zerofill = 'false'
        if self.scenario_number % freq == 0:
            zerofill = 'true'
        self.backup_dir = os.path.join(self.home, "WT_BACKUP")
        conn_params = \
                'log=(archive=false,enabled,file_max=%s),' % self.logmax + \
                'log=(zero_fill=%s),' % zerofill + \
                'create,error_prefix="%s: ",' % self.shortid() + \
                'transaction_sync="%s",' % self.txn_sync
        # print "Creating conn at '%s' with config '%s'" % (dir, conn_params)
        conn = wiredtiger_open(dir, conn_params)
        self.pr( ` conn `)
        self.session2 = conn.open_session()
        return conn

    # Check that a cursor (optionally started in a new transaction), sees the
    # expected values.
    def check(self, session, txn_config, expected):
        if txn_config:
            session.begin_transaction(txn_config)
        c = session.open_cursor(self.uri, None)
        actual = dict((k, v) for k, v in c if v != 0)
        # Search for the expected items as well as iterating
        for k, v in expected.iteritems():
            self.assertEqual(c[k], v)
        c.close()
        if txn_config:
            session.commit_transaction()
        self.assertEqual(actual, expected)

    # Check the state of the system with respect to the current cursor and
    # different isolation levels.
    def check_all(self, current, committed):
        # Transactions see their own changes.
        # Read-uncommitted transactions see all changes.
        # Snapshot and read-committed transactions should not see changes.
        self.check(self.session, None, current)
        self.check(self.session2, "isolation=snapshot", committed)
        self.check(self.session2, "isolation=read-committed", committed)
        self.check(self.session2, "isolation=read-uncommitted", current)

        # Opening a clone of the database home directory should run
        # recovery and see the committed results.
        self.backup(self.backup_dir)
        backup_conn_params = 'log=(enabled,file_max=%s)' % self.logmax
        backup_conn = wiredtiger_open(self.backup_dir, backup_conn_params)
        try:
            self.check(backup_conn.open_session(), None, committed)
        finally:
            backup_conn.close()

    def check_log(self, committed):
        self.backup(self.backup_dir)
        #
        # Open and close the backup connection a few times to force
        # repeated recovery and log archiving even if later recoveries
        # are essentially no-ops. Confirm that the backup contains
        # the committed operations after recovery.
        #
        # Cycle through the different archive values in a
        # deterministic manner.
        self.archive = self.archive_list[self.scenario_number %
                                         len(self.archive_list)]
        backup_conn_params = \
            'log=(enabled,file_max=%s,archive=%s)' % (self.logmax, self.archive)
        orig_logs = fnmatch.filter(os.listdir(self.backup_dir), "*Log*")
        endcount = 2
        count = 0
        while count < endcount:
            backup_conn = wiredtiger_open(self.backup_dir, backup_conn_params)
            try:
                self.check(backup_conn.open_session(), None, committed)
            finally:
                # Sleep long enough so that the archive thread is guaranteed
                # to run before we close the connection.
                time.sleep(1.0)
                backup_conn.close()
            count += 1
        #
        # Check logs after repeated openings. The first log should
        # have been archived if configured. Subsequent openings would not
        # archive because no checkpoint is written due to no modifications.
        #
        cur_logs = fnmatch.filter(os.listdir(self.backup_dir), "*Log*")
        for o in orig_logs:
            if self.archive == 'true':
                self.assertEqual(False, o in cur_logs)
            else:
                self.assertEqual(True, o in cur_logs)
        #
        # Run printlog and make sure it exits with zero status.
        # Printlog should not run recovery nor advance the logs.  Make sure
        # it does not.
        #
        self.runWt(['-h', self.backup_dir, 'printlog'],
                   outfilename='printlog.out')
        pr_logs = fnmatch.filter(os.listdir(self.backup_dir), "*Log*")
        self.assertEqual(cur_logs, pr_logs)

    def test_ops(self):
        # print "Creating %s with config '%s'" % (self.uri, self.create_params)
        self.session.create(self.uri, self.create_params)
        # Set up the table with entries for 1, 2, 10 and 11.
        # We use the overwrite config so insert can update as needed.
        c = self.session.open_cursor(self.uri, None, 'overwrite')
        c[1] = c[2] = c[10] = c[11] = 1
        current = {1: 1, 2: 1, 10: 1, 11: 1}
        committed = current.copy()

        reopen = self.conn_list[self.scenario_number % len(self.conn_list)]
        ops = (self.op1, self.op2, self.op3, self.op4)
        txns = (self.txn1, self.txn2, self.txn3, self.txn4)
        # for ok, txn in zip(ops, txns):
        # print ', '.join('%s(%d)[%s]' % (ok[0], ok[1], txn)
        for i, ot in enumerate(zip(ops, txns)):
            ok, txn = ot
            op, k = ok

            # Close and reopen the connection and cursor.
            if reopen == 'reopen':
                self.reopen_conn()
                c = self.session.open_cursor(self.uri, None, 'overwrite')

            self.session.begin_transaction(
                (self.scenario_number % 2) and 'sync' or None)
            # Test multiple operations per transaction by always
            # doing the same operation on key k + 1.
            k1 = k + 1
            # print '%d: %s(%d)[%s]' % (i, ok[0], ok[1], txn)
            if op == 'insert' or op == 'update':
                c[k] = c[k1] = i + 2
                current[k] = current[k1] = i + 2
            elif op == 'remove':
                c.set_key(k)
                c.remove()
                c.set_key(k1)
                c.remove()
                if k in current:
                    del current[k]
                if k1 in current:
                    del current[k1]

            # print current
            # Check the state after each operation.
            self.check_all(current, committed)

            if txn == 'commit':
                committed = current.copy()
                self.session.commit_transaction()
            elif txn == 'rollback':
                current = committed.copy()
                self.session.rollback_transaction()

            # Check the state after each commit/rollback.
            self.check_all(current, committed)

        # check_log() is slow, we don't run it on every scenario.
        if self.scenario_number % test_txn02.checklog_mod == 0:
            self.check_log(committed)
コード例 #5
0
    def test_timestamp_randomizer(self):
        # Local function to generate a random timestamp, or return -1
        def maybe_ts(do_gen, iternum):
            if do_gen:
                return self.gen_ts(iternum)
            else:
                return -1

        if wttest.islongtest():
            iterations = 100000
        else:
            iterations = 1000

        create_params = 'key_format={},value_format={}'.format(
            self.key_format, self.value_format)
        self.session.create(self.uri, create_params)

        self.set_global_timestamps(1, 1, -1)

        # Create tables with no entries
        ds = SimpleDataSet(self,
                           self.uri,
                           0,
                           key_format=self.key_format,
                           value_format=self.value_format)

        # We do a bunch of iterations, doing transactions, prepare, and global timestamp calls
        # with timestamps that are sometimes valid, sometimes not. We use the iteration number
        # as an "approximate timestamp", and generate timestamps for our calls that are near
        # that number (within 10).  Thus, as the test runs, the timestamps generally get larger.
        # We always know the state of global timestamps, so we can predict the success/failure
        # on each call.
        self.commit_value = '<NOT_SET>'
        for iternum in range(1, iterations):
            self.pr('\n===== ITERATION ' + str(iternum) + '/' +
                    str(iterations))
            self.pr('RANDOM: ({0},{1})'.format(self.rand.seedw,
                                               self.rand.seedz))
            if self.rand.rand32() % 10 != 0:
                commit_ts = self.gen_ts(iternum)
                durable_ts = self.gen_ts(iternum)
                do_prepare = (self.rand.rand32() % 20 == 0)
                if self.rand.rand32() % 2 == 0:
                    read_ts = self.gen_ts(iternum)
                else:
                    read_ts = -1  # no read_timestamp used in txn

                # OOD does not work with prepared updates. Hence, the commit ts should always be
                # greater than the last durable ts.
                if commit_ts <= self.last_durable:
                    commit_ts = self.last_durable + 1

                if do_prepare:
                    # If we doing a prepare, we must abide by some additional rules.
                    # If we don't we'll immediately panic
                    if commit_ts < self.oldest_ts:
                        commit_ts = self.oldest_ts
                    if durable_ts < commit_ts:
                        durable_ts = commit_ts
                    if durable_ts <= self.stable_ts:
                        durable_ts = self.stable_ts + 1
                value = self.gen_value(iternum, commit_ts)
                self.updates(value, ds, do_prepare, commit_ts, durable_ts,
                             read_ts)

            if self.rand.rand32() % 2 == 0:
                # Set some combination of the global timestamps
                r = self.rand.rand32() % 16
                oldest = maybe_ts((r & 0x1) != 0, iternum)
                stable = maybe_ts((r & 0x2) != 0, iternum)
                commit = maybe_ts((r & 0x4) != 0, iternum)
                durable = maybe_ts((r & 0x8) != 0, iternum)
                self.set_global_timestamps(oldest, stable, durable)

        # Make sure the resulting rows are what we expect.
        cursor = self.session.open_cursor(self.uri)
        expect_key = 1
        expect_value = self.commit_value
        for k, v in cursor:
            self.assertEquals(k, expect_key)
            self.assertEquals(v, expect_value)
            expect_key += 1

        # Although it's theoretically possible to never successfully update a single row,
        # with a large number of iterations that should never happen.  I'd rather catch
        # a test code error where we mistakenly don't update any rows.
        self.assertGreater(expect_key, 1)
        cursor.close()
コード例 #6
0
class test_cursor_bound_fuzz(wttest.WiredTigerTestCase):
    file_name = 'test_fuzz.wt'
    
    iteration_count = 200 if wttest.islongtest() else 50
    # For each iteration we do search_count searches that way we test more cases without having to
    # generate as many key ranges.
    search_count = 20
    key_count = 10000 if wttest.islongtest() else 1000
    # Large transactions throw rollback errors so we don't use them in the long test.
    transactions_enabled = False if wttest.islongtest() else True
    value_size = 100000 if wttest.islongtest() else 100
    prepare_frequency = 5/100
    update_frequency = 2/10
    
    min_key = 1
    # Max_key is not inclusive so the actual max_key is max_key - 1.
    max_key = min_key + key_count
    # A lot of time was spent generating values, to achieve some amount of randomness we pre
    # generate N values and keep them in memory.
    value_array = []
    value_array_size = 20
    current_ts = 1
    applied_ops = False
    key_range = {}

    types = [
        ('file', dict(uri='file:')),
        ('table', dict(uri='table:'))
    ]

    data_format = [
        ('row', dict(key_format='i')),
        ('column', dict(key_format='r'))
    ]
    scenarios = make_scenarios(types, data_format)

    # Iterates valid keys from min_key to max_key, the maximum key is defined as max_key - 1.
    # Python doesn't consider the end of the range as inclusive.
    def key_range_iter(self):
        for i in range(self.min_key, self.max_key):
            yield i

    def dump_key_range(self):
        for i in self.key_range_iter():
            self.pr(self.key_range[i].to_string())

    # Generate a random ascii value.
    def generate_value(self):
        return ''.join(random.choice(string.ascii_lowercase) for _ in range(self.value_size))

    # Get a value from the value array.
    def get_value(self):
        return self.value_array[random.randrange(self.value_array_size)]

    # Get a key within the range of min_key and max_key.
    def get_random_key(self):
        return random.randrange(self.min_key, self.max_key)

    # Update a key using the cursor and update its in memory representation.
    def apply_update(self, cursor, key_id, prepare):
        value = self.get_value()
        cursor[key_id] = value
        self.key_range[key_id].update(value, key_states.UPSERTED, self.current_ts, prepare)
        self.verbose(3, "Updating " + self.key_range[key_id].to_string())

    # Remove a key using the cursor and mark it as deleted in memory.
    # If the key is already deleted we skip the remove.
    def apply_remove(self, cursor, key_id, prepare):
        if (self.key_range[key_id].is_deleted()):
            return
        cursor.set_key(key_id)
        self.assertEqual(cursor.remove(), 0)
        self.key_range[key_id].update(None, key_states.DELETED, self.current_ts, prepare)
        self.verbose(3, "Removing " + self.key_range[key_id].to_string())

    # Apply a truncate operation to the key range.
    def apply_truncate(self, session, cursor, cursor2, prepare):
        lower_key = self.get_random_key()
        if (lower_key + 1 < self.max_key):
            upper_key = random.randrange(lower_key + 1, self.max_key)
            cursor.set_key(lower_key)
            cursor2.set_key(upper_key)
            self.assertEqual(session.truncate(None, cursor, cursor2, None), 0)

            # Mark all keys from lower_key to upper_key as deleted.
            for key_id in range(lower_key, upper_key + 1):
                self.key_range[key_id].update(None, key_states.DELETED, self.current_ts, prepare)

            self.verbose(2, "Truncated keys between: " + str(lower_key) + " and: " + str(upper_key))

    # Each iteration calls this function once to update the state of the keys in the database and
    # in memory.
    def apply_ops(self, session, cursor, prepare):
        op = random.choice(list(operations))
        if (op is operations.TRUNCATE and self.applied_ops):
            cursor2 = session.open_cursor(self.uri + self.file_name)
            self.apply_truncate(session, cursor, cursor2, prepare)
        else:
            for i in self.key_range_iter():
                if (random.uniform(0, 1) < self.update_frequency):
                    continue
                op = random.choice(list(operations))
                if (op is operations.TRUNCATE):
                    pass
                elif (op is operations.UPSERT):
                    self.apply_update(cursor, i, prepare)
                elif (op is operations.REMOVE):
                    self.apply_remove(cursor, i, prepare)
                else:
                    raise Exception("Unhandled operation generated")
        self.applied_ops = True

    # As prepare throws a prepare conflict exception we wrap the call to anything that could
    # encounter a prepare conflict in a try except, we then return the error code to the caller.
    def prepare_call(self, func):
        try:
            ret = func()
        except wiredtiger.WiredTigerError as e:
            if wiredtiger.wiredtiger_strerror(wiredtiger.WT_PREPARE_CONFLICT) in str(e):
                ret = wiredtiger.WT_PREPARE_CONFLICT
            else:
                raise e
        return ret
    
    # Once we commit the prepared transaction, update and clear the prepared flags.
    def clear_prepare_key_ranges(self):
        for i in self.key_range_iter():
            self.key_range[i].clear_prepared()

    # Given a bound, this functions returns the start or end expected key of the bounded range. 
    # Note the type argument determines if we return the start or end limit. e.g. if we have a lower
    # bound then the key would be the lower bound, however if the lower bound isn't enabled then the
    # lowest possible key would be min_key. max_key isn't inclusive so we subtract 1 off it.
    def get_expected_limit_key(self, bound_set, type):
        if (type == bound_type.LOWER):
            if (bound_set.lower.enabled):
                if (bound_set.lower.inclusive):
                    return bound_set.lower.key
                return bound_set.lower.key + 1
            return self.min_key
        if (bound_set.upper.enabled):
            if (bound_set.upper.inclusive):
                return bound_set.upper.key
            return bound_set.upper.key - 1
        return self.max_key - 1

    # When a prepared cursor walks next or prev it can skip deleted records internally before
    # returning a prepare conflict, we don't know which key it got to so we need to validate that
    # we see a series of deleted keys followed by a prepared key.
    def validate_deleted_prepared_range(self, start_key, end_key, next):
        if (next):
            step = 1
        else:
            step = -1
        self.verbose(2, "Walking deleted range from: " + str(start_key) + " to: " + str(end_key))
        for i in range(start_key, end_key, step):
            self.verbose(3, "Validating state of key: " + self.key_range[i].to_string())
            if (self.key_range[i].is_prepared()):
                return
            elif (self.key_range[i].is_deleted()):
                continue
            else:   
                self.assertTrue(False)

    # Validate a prepare conflict in the cursor->next scenario.
    def validate_prepare_conflict_next(self, current_key, bound_set):
        self.verbose(3, "Current key is: " + str(current_key) + " min_key is: " + str(self.min_key))
        start_range = None
        if current_key == self.min_key:
            # We hit a prepare conflict while walking forwards before we stepped to a valid key.
            # Therefore validate all the keys from start of the range are deleted followed by a prepare.
            start_range = self.get_expected_limit_key(bound_set, bound_type.LOWER)
        else:
            # We walked part of the way through a valid key range before we hit the prepared
            # update. Therefore validate the range between our current key and the
            # end range.
            start_range = current_key   
        end_range = self.get_expected_limit_key(bound_set, bound_type.UPPER)

        # Perform validation from the start range to end range.
        self.validate_deleted_prepared_range(start_range, end_range, True)

    # Validate a prepare conflict in the cursor->prev scenario.
    def validate_prepare_conflict_prev(self, current_key, bound_set):
        self.verbose(3, "Current key is: " + str(current_key) + " max_key is: " + str(self.max_key))
        start_range = None
        if current_key == self.max_key - 1:
            # We hit a prepare conflict while walking backwards before we stepped to a valid key.
            # Therefore validate all the keys from start of the range are deleted followed by a
            # prepare.
            start_range = self.get_expected_limit_key(bound_set, bound_type.UPPER)
        else:
            # We walked part of the way through a valid key range before we hit the prepared
            # update. Therefore validate the range between our current key and the
            # end range.
            start_range = current_key  
        end_range = self.get_expected_limit_key(bound_set, bound_type.LOWER)

        # Perform validation from the start range to end range.
        self.validate_deleted_prepared_range(start_range, end_range, False)

    # Walk the cursor using cursor->next and validate the returned keys.
    def run_next(self, bound_set, cursor):
        # This array gives us confidence that we have validated the full key range.
        checked_keys = []
        self.verbose(2, "Running scenario: NEXT")
        key_range_it = self.min_key - 1
        ret = self.prepare_call(lambda: cursor.next())
        while (ret != wiredtiger.WT_NOTFOUND and ret != wiredtiger.WT_PREPARE_CONFLICT):
            current_key = cursor.get_key()
            current_value = cursor.get_value()
            self.verbose(3, "Cursor next walked to key: " + str(current_key) + " value: " + current_value)
            self.assertTrue(bound_set.in_bounds_key(current_key))
            self.assertTrue(self.key_range[current_key].equals(current_key, current_value))
            checked_keys.append(current_key)
            # If the cursor has walked to a record that isn't +1 our current record then it
            # skipped something internally.
            # Check that the key range between key_range_it and current_key isn't visible
            if (current_key != key_range_it + 1):
                for i in range(key_range_it + 1, current_key):
                    self.verbose(3, "Checking key is deleted or oob: " + str(i))
                    checked_keys.append(i)
                    self.assertTrue(self.key_range[i].is_deleted_or_oob(bound_set))
            key_range_it = current_key
            ret = self.prepare_call(lambda: cursor.next())
        key_range_it = key_range_it + 1
        # If we were returned a prepare conflict it means the cursor has found a prepared key/value.
        # We need to validate that it arrived there correctly using the in memory state of the
        # database. We cannot continue from a prepare conflict so we return.
        if (ret == wiredtiger.WT_PREPARE_CONFLICT):
            self.validate_prepare_conflict_next(key_range_it, bound_set)
            return
        # If key_range_it is < key_count then the rest of the range was deleted
        # Remember to increment it by one to get it to the first not in bounds key.
        for i in range(key_range_it, self.max_key):
            checked_keys.append(i)
            self.verbose(3, "Checking key is deleted or oob: " + str(i))
            self.assertTrue(self.key_range[i].is_deleted_or_oob(bound_set))
        self.assertTrue(len(checked_keys) == self.key_count)

    # Walk the cursor using cursor->prev and validate the returned keys.
    def run_prev(self, bound_set, cursor):
        # This array gives us confidence that we have validated the full key range.
        checked_keys = []
        self.verbose(2, "Running scenario: PREV")
        ret = self.prepare_call(lambda: cursor.prev())
        key_range_it = self.max_key
        while (ret != wiredtiger.WT_NOTFOUND and ret != wiredtiger.WT_PREPARE_CONFLICT):
            current_key = cursor.get_key()
            current_value = cursor.get_value()
            self.verbose(3, "Cursor prev walked to key: " + str(current_key) + " value: " + current_value)
            self.assertTrue(bound_set.in_bounds_key(current_key))
            self.assertTrue(self.key_range[current_key].equals(current_key, current_value))
            checked_keys.append(current_key)
            
            # If the cursor has walked to a record that isn't -1 our current record then it
            # skipped something internally.
            # Check that the key range between key_range_it and current_key isn't visible
            if (current_key != key_range_it - 1):
                # Check that the key range between key_range_it and current_key isn't visible
                for i in range(current_key + 1, key_range_it):
                    self.verbose(3, "Checking key is deleted or oob: " + str(i))
                    checked_keys.append(i)
                    self.assertTrue(self.key_range[i].is_deleted_or_oob(bound_set))
            key_range_it = current_key
            ret = self.prepare_call(lambda: cursor.prev())
        # If key_range_it is > key_count then the rest of the range was deleted
        key_range_it -= 1
        if (ret == wiredtiger.WT_PREPARE_CONFLICT):
            self.validate_prepare_conflict_prev(key_range_it, bound_set)
            return
        for i in range(self.min_key, key_range_it + 1):
            checked_keys.append(i)
            self.verbose(3, "Checking key is deleted or oob: " + str(i))
            self.assertTrue(self.key_range[i].is_deleted_or_oob(bound_set))
        self.assertTrue(len(checked_keys) == self.key_count)

    # Run basic cursor->search() scenarios and validate the outcome.
    def run_search(self, bound_set, cursor):
        # Choose a N random keys and perform a search on each
        for i in range(0, self.search_count):
            search_key = self.get_random_key()
            cursor.set_key(search_key)
            ret = self.prepare_call(lambda: cursor.search())
            if (ret == wiredtiger.WT_PREPARE_CONFLICT):
                self.assertTrue(self.key_range[search_key].is_prepared())
            elif (ret == wiredtiger.WT_NOTFOUND):
                self.assertTrue(self.key_range[search_key].is_deleted_or_oob(bound_set))
            elif (ret == 0):
                # Assert that the key exists, and is within the range.
                self.assertTrue(self.key_range[search_key].equals(cursor.get_key(), cursor.get_value()))
                self.assertTrue(bound_set.in_bounds_key(cursor.get_key()))
            else:
                raise Exception('Unhandled error returned by search')

    # Check that all the keys within the given bound_set are deleted.
    def check_all_within_bounds_not_visible(self, bound_set):
        for i in range(bound_set.start_range(self.min_key), bound_set.end_range(self.max_key)):
            self.verbose(3, "checking key: " +self.key_range[i].to_string())
            if (not self.key_range[i].is_deleted()):
                return False
        return True

    # Run a cursor->search_near scenario and validate that the outcome was correct.
    def run_search_near(self, bound_set, cursor):
        # Choose N random keys and perform a search near.
        for i in range(0, self.search_count):
            search_key = self.get_random_key()
            cursor.set_key(search_key)
            self.verbose(2, "Searching for key: " + str(search_key))
            ret = self.prepare_call(lambda: cursor.search_near())
            if (ret == wiredtiger.WT_NOTFOUND):
                self.verbose(2, "Nothing visible checking.")
                # Nothing visible within the bound range.
                # Validate.
            elif (ret == wiredtiger.WT_PREPARE_CONFLICT):
                # Due to the complexity of the search near logic we will simply check if there is
                # a prepared key within the range.
                found_prepare = False
                for i in range(bound_set.start_range(self.min_key), bound_set.end_range(self.max_key)):
                    if (self.key_range[i].is_prepared()):
                        found_prepare = True
                        break
                self.assertTrue(found_prepare)
                self.verbose(2, "Received prepare conflict in search near.")
            else:
                key_found = cursor.get_key()
                self.verbose(2, "Found a key: " + str(key_found))
                current_key = key_found
                # Assert the value we found matches.
                # Equals also validates that the key is visible.
                self.assertTrue(self.key_range[current_key].equals(current_key, cursor.get_value()))
                if (bound_set.in_bounds_key(search_key)):
                    # We returned a key within the range, validate that key is the one that
                    # should've been returned.
                    if (key_found == search_key):
                        # We've already deteremined the key matches. We can return.
                        pass
                    if (key_found > search_key):
                        # Walk left and validate that all isn't visible to the search key.
                        while (current_key != search_key):
                            current_key = current_key - 1
                            self.assertTrue(self.key_range[current_key].is_deleted())
                    if (key_found < search_key):
                        # Walk right and validate that all isn't visible to the search key.
                        while (current_key != search_key):
                            current_key = current_key + 1
                            self.assertTrue(self.key_range[current_key].is_deleted())
                else:
                    # We searched for a value outside our range, we should return whichever value
                    # is closest within the range.
                    if (bound_set.lower.enabled and search_key <= bound_set.lower.key):
                        # We searched to the left of our bounds. In the equals case the lower bound
                        # must not be inclusive.
                        # Validate that the we returned the nearest value to the lower bound.
                        if (bound_set.lower.inclusive):
                            self.assertTrue(key_found >= bound_set.lower.key)
                            current_key = bound_set.lower.key
                        else:
                            self.assertTrue(key_found > bound_set.lower.key)
                            current_key = bound_set.lower.key + 1
                        while (current_key != key_found):
                            self.assertTrue(self.key_range[current_key].is_deleted())
                            current_key = current_key + 1
                    elif (bound_set.upper.enabled and search_key >= bound_set.upper.key):
                        # We searched to the right of our bounds. In the equals case the upper bound
                        # must not be inclusive.
                        # Validate that the we returned the nearest value to the upper bound.
                        if (bound_set.upper.inclusive):
                            self.assertTrue(key_found <= bound_set.upper.key)
                            current_key = bound_set.upper.key
                        else:
                            self.assertTrue(key_found < bound_set.upper.key)
                            current_key = bound_set.upper.key - 1
                        while (current_key != key_found):
                            self.assertTrue(self.key_range[current_key].is_deleted())
                            current_key = current_key - 1
                    else:
                        raise Exception('Illegal state found in search_near')

    # Choose a scenario and run it.
    def run_bound_scenarios(self, bound_set, cursor):
        scenario = random.choice(list(bound_scenarios))
        if (scenario is bound_scenarios.NEXT):
            self.run_next(bound_set, cursor)
        elif (scenario is bound_scenarios.PREV):
            self.run_prev(bound_set, cursor)
        elif (scenario is bound_scenarios.SEARCH):
            self.run_search(bound_set, cursor)
        elif (scenario is bound_scenarios.SEARCH_NEAR):
            self.run_search_near(bound_set, cursor)
        else:
            raise Exception('Unhandled bound scenario chosen')

    # Generate a set of bounds and apply them to the cursor.
    def apply_bounds(self, cursor):
        cursor.reset()
        lower = bound(self.get_random_key(), bool(random.getrandbits(1)), bool(random.getrandbits(1)))
        upper = bound(random.randrange(lower.key, self.max_key), bool(random.getrandbits(1)), bool(random.getrandbits(1)))
        # Prevent invalid bounds being generated.
        if (lower.key == upper.key and lower.enabled and upper.enabled):
            lower.inclusive = upper.inclusive = True
        bound_set = bounds(lower, upper)
        if (lower.enabled):
            cursor.set_key(lower.key)
            cursor.bound("bound=lower,inclusive=" + lower.inclusive_str())
        if (upper.enabled):
            cursor.set_key(upper.key)
            cursor.bound("bound=upper,inclusive=" + upper.inclusive_str())
        return bound_set

    # The primary test loop is contained here.
    def test_bound_fuzz(self):
        uri = self.uri + self.file_name
        create_params = 'value_format=S,key_format={}'.format(self.key_format)
        # Reset the key range for every scenario.
        self.key_range = {}
        # Setup a reproducible random seed.
        # If this test fails inspect the file WT_TEST/results.txt and replace the time.time()
        # with a given seed. e.g.:
        # seed = 1660215872.5926154
        # Additionally this test is configured for verbose logging which can make debugging a bit
        # easier.
        seed = time.time()
        self.pr("Using seed: " + str(seed))
        random.seed(seed)
        self.session.create(uri, create_params)
        read_cursor = self.session.open_cursor(uri)

        write_session = self.setUpSessionOpen(self.conn)
        write_cursor = write_session.open_cursor(uri)

        # Initialize the value array.
        self.verbose(2, "Generating value array")
        for i in range(0, self.value_array_size):
            self.value_array.append(self.generate_value())

        # Initialize the key range.
        for i in self.key_range_iter():
            key_value = self.get_value()
            self.key_range[i] = key(i, key_value, key_states.UPSERTED, self.current_ts)
            self.current_ts += 1
            if (self.transactions_enabled):
                write_session.begin_transaction()
            write_cursor[i] = key_value
            if (self.transactions_enabled):
                write_session.commit_transaction('commit_timestamp=' + self.timestamp_str(self.key_range[i].timestamp))
        self.session.checkpoint()

        # Begin main loop
        for  i in range(0, self.iteration_count):
            self.verbose(2, "Iteration: " + str(i))
            bound_set = self.apply_bounds(read_cursor)
            self.verbose(2, "Generated bound set: " + bound_set.to_string())

            # Check if we are doing a prepared transaction on this iteration.
            prepare = random.uniform(0, 1) <= self.prepare_frequency and self.transactions_enabled
            if (self.transactions_enabled):
                write_session.begin_transaction()
            self.apply_ops(write_session, write_cursor, prepare)
            if (self.transactions_enabled):
                if (prepare):
                    self.verbose(2, "Preparing applied operations.")
                    write_session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(self.current_ts))
                else:
                    write_session.commit_transaction('commit_timestamp=' + self.timestamp_str(self.current_ts))

            # Use the current timestamp so we don't need to track previous versions.
            if (self.transactions_enabled):
                self.session.begin_transaction('read_timestamp=' + self.timestamp_str(self.current_ts))
            self.run_bound_scenarios(bound_set, read_cursor)
            if (self.transactions_enabled):
                self.session.rollback_transaction()
                if (prepare):
                    write_session.commit_transaction(
                        'commit_timestamp=' + self.timestamp_str(self.current_ts) +
                        ',durable_timestamp='+ self.timestamp_str(self.current_ts))
                    self.clear_prepare_key_ranges()
            self.current_ts += 1
            if (i % 10 == 0):
                # Technically this is a write but easier to do it with this session.
                self.session.checkpoint()
コード例 #7
0
class test_backup26(backup_base):
    dir = 'backup.dir'  # Backup directory name
    uri = "table_backup"
    ntables = 10000 if wttest.islongtest() else 500

    # Reverse the backup restore list, WiredTiger should still succeed in this case.
    reverse = [
        ["reverse_target_list", dict(reverse=True)],
        ["target_list", dict(reverse=False)],
    ]

    # Percentage of tables to not copy over in selective backup.
    percentage = [
        ('hundred_precent', dict(percentage=1)),
        ('ninety_percent', dict(percentage=0.9)),
        ('fifty_percent', dict(percentage=0.5)),
        ('ten_percent', dict(percentage=0.1)),
        ('zero_percent', dict(percentage=0)),
    ]
    scenarios = make_scenarios(percentage, reverse)

    def test_backup26(self):
        selective_remove_uri_file_list = []
        selective_remove_uri_list = []
        selective_uri_list = []

        for i in range(0, self.ntables):
            uri = "table:{0}".format(self.uri + str(i))
            dataset = SimpleDataSet(self, uri, 100, key_format="S")
            dataset.populate()
            # Append the table uri to the selective backup remove list until the set percentage.
            # These tables will not be copied over in selective backup.
            if (i <= int(self.ntables * self.percentage)):
                selective_remove_uri_list.append(uri)
                selective_remove_uri_file_list.append(
                    "{0}.wt".format(self.uri + str(i)))
            else:
                selective_uri_list.append(uri)
        self.session.checkpoint()

        os.mkdir(self.dir)

        # Now copy the files using full backup. This should not include the tables inside the remove list.
        all_files = self.take_selective_backup(self.dir,
                                               selective_remove_uri_file_list)

        target_uris = None
        if self.reverse:
            target_uris = str(selective_uri_list[::-1]).replace("\'", "\"")
        else:
            target_uris = str(selective_uri_list).replace("\'", "\"")
        starttime = time.time()
        # After the full backup, open and recover the backup database.
        backup_conn = self.wiredtiger_open(
            self.dir, "backup_restore_target={0}".format(target_uris))
        elapsed = time.time() - starttime
        self.pr("%s partial backup has taken %.2f seconds." %
                (str(self), elapsed))

        bkup_session = backup_conn.open_session()
        # Open the cursor from uris that were not part of the selective backup and expect failure
        # since file doesn't exist.
        for remove_uri in selective_remove_uri_list:
            self.assertRaisesException(
                wiredtiger.WiredTigerError,
                lambda: bkup_session.open_cursor(remove_uri, None, None))

        # Open the cursors on tables that copied over to the backup directory. They should still
        # recover properly.
        for uri in selective_uri_list:
            c = bkup_session.open_cursor(uri, None, None)
            ds = SimpleDataSet(self, uri, 100, key_format="S")
            ds.check_cursor(c)
            c.close()
        backup_conn.close()