1#!/usr/bin/env python 2# 3# Public Domain 2014-2018 MongoDB, Inc. 4# Public Domain 2008-2014 WiredTiger, Inc. 5# 6# This is free and unencumbered software released into the public domain. 7# 8# Anyone is free to copy, modify, publish, use, compile, sell, or 9# distribute this software, either in source code form or as a compiled 10# binary, for any purpose, commercial or non-commercial, and by any 11# means. 12# 13# In jurisdictions that recognize copyright laws, the author or authors 14# of this software dedicate any and all copyright interest in the 15# software to the public domain. We make this dedication for the benefit 16# of the public at large and to the detriment of our heirs and 17# successors. We intend this dedication to be an overt act of 18# relinquishment in perpetuity of all present and future rights to this 19# software under copyright law. 20# 21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 25# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 26# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27# OTHER DEALINGS IN THE SOFTWARE. 28# 29# test_txn05.py 30# Transactions: commits and rollbacks 31# 32 33import fnmatch, os, shutil, time 34from suite_subprocess import suite_subprocess 35from wtscenario import make_scenarios 36import wttest 37 38class test_txn05(wttest.WiredTigerTestCase, suite_subprocess): 39 logmax = "100K" 40 tablename = 'test_txn05' 41 uri = 'table:' + tablename 42 archive_list = ['true', 'false'] 43 sync_list = [ 44 '(method=dsync,enabled)', 45 '(method=fsync,enabled)', 46 '(method=none,enabled)', 47 '(enabled=false)' 48 ] 49 50 types = [ 51 ('row', dict(tabletype='row', 52 create_params = 'key_format=i,value_format=i')), 53 ('var', dict(tabletype='var', 54 create_params = 'key_format=r,value_format=i')), 55 ('fix', dict(tabletype='fix', 56 create_params = 'key_format=r,value_format=8t')), 57 ] 58 op1s = [ 59 ('trunc-all', dict(op1=('all', 0))), 60 ('trunc-both', dict(op1=('both', 2))), 61 ('trunc-start', dict(op1=('start', 2))), 62 ('trunc-stop', dict(op1=('stop', 2))), 63 ] 64 txn1s = [('t1c', dict(txn1='commit')), ('t1r', dict(txn1='rollback'))] 65 66 scenarios = make_scenarios(types, op1s, txn1s) 67 68 def conn_config(self): 69 # Cycle through the different transaction_sync values in a 70 # deterministic manner. 71 txn_sync = self.sync_list[ 72 self.scenario_number % len(self.sync_list)] 73 # Set archive false on the home directory. 74 return 'log=(archive=false,enabled,file_max=%s),' % self.logmax + \ 75 'transaction_sync="%s",' % txn_sync 76 77 # Check that a cursor (optionally started in a new transaction), sees the 78 # expected values. 79 def check(self, session, txn_config, expected): 80 if txn_config: 81 session.begin_transaction(txn_config) 82 c = session.open_cursor(self.uri, None) 83 actual = dict((k, v) for k, v in c if v != 0) 84 # Search for the expected items as well as iterating 85 for k, v in expected.iteritems(): 86 self.assertEqual(c[k], v) 87 c.close() 88 if txn_config: 89 session.commit_transaction() 90 self.assertEqual(actual, expected) 91 92 # Check the state of the system with respect to the current cursor and 93 # different isolation levels. 94 def check_all(self, current, committed): 95 # Transactions see their own changes. 96 # Read-uncommitted transactions see all changes. 97 # Snapshot and read-committed transactions should not see changes. 98 self.check(self.session, None, current) 99 self.check(self.session2, "isolation=snapshot", committed) 100 self.check(self.session2, "isolation=read-committed", committed) 101 self.check(self.session2, "isolation=read-uncommitted", current) 102 103 # Opening a clone of the database home directory should run 104 # recovery and see the committed results. Flush the log because 105 # the backup may not get all the log records if we are running 106 # without a sync option. Use sync=off to force a write to the OS. 107 self.session.log_flush('sync=off') 108 self.backup(self.backup_dir) 109 backup_conn_params = 'log=(enabled,file_max=%s)' % self.logmax 110 backup_conn = self.wiredtiger_open(self.backup_dir, backup_conn_params) 111 try: 112 self.check(backup_conn.open_session(), None, committed) 113 finally: 114 backup_conn.close() 115 116 def check_log(self, committed): 117 self.backup(self.backup_dir) 118 # 119 # Open and close the backup connection a few times to force 120 # repeated recovery and log archiving even if later recoveries 121 # are essentially no-ops. Confirm that the backup contains 122 # the committed operations after recovery. 123 # 124 # Cycle through the different archive values in a 125 # deterministic manner. 126 self.archive = self.archive_list[ 127 self.scenario_number % len(self.archive_list)] 128 backup_conn_params = \ 129 'log=(enabled,file_max=%s,archive=%s)' % (self.logmax, self.archive) 130 orig_logs = fnmatch.filter(os.listdir(self.backup_dir), "*gerLog*") 131 endcount = 2 132 count = 0 133 while count < endcount: 134 backup_conn = self.wiredtiger_open(self.backup_dir, 135 backup_conn_params) 136 try: 137 session = backup_conn.open_session() 138 finally: 139 self.check(session, None, committed) 140 # Sleep long enough so that the archive thread is guaranteed 141 # to run before we close the connection. 142 time.sleep(1.0) 143 if count == 0: 144 first_logs = \ 145 fnmatch.filter(os.listdir(self.backup_dir), "*gerLog*") 146 backup_conn.close() 147 count += 1 148 # 149 # Check logs after repeated openings. The first log should 150 # have been archived if configured. Subsequent openings would not 151 # archive because no checkpoint is written due to no modifications. 152 # 153 cur_logs = fnmatch.filter(os.listdir(self.backup_dir), "*gerLog*") 154 for o in orig_logs: 155 # Creating the backup was effectively an unclean shutdown so 156 # even after sleeping, we should never archive log files 157 # because a checkpoint has not run. Later opens and runs of 158 # recovery will detect a clean shutdown and allow archiving. 159 self.assertEqual(True, o in first_logs) 160 if self.archive == 'true': 161 self.assertEqual(False, o in cur_logs) 162 else: 163 self.assertEqual(True, o in cur_logs) 164 # 165 # Run printlog and make sure it exits with zero status. 166 # 167 self.runWt(['-h', self.backup_dir, 'printlog'], outfilename='printlog.out') 168 169 def test_ops(self): 170 self.backup_dir = os.path.join(self.home, "WT_BACKUP") 171 self.session2 = self.conn.open_session() 172 # print "Creating %s with config '%s'" % (self.uri, self.create_params) 173 self.session.create(self.uri, self.create_params) 174 # Set up the table with entries for 1-5. 175 # We then truncate starting or ending in various places. 176 c = self.session.open_cursor(self.uri, None) 177 current = {1:1, 2:1, 3:1, 4:1, 5:1} 178 for k in current: 179 c[k] = 1 180 committed = current.copy() 181 182 ops = (self.op1, ) 183 txns = (self.txn1, ) 184 for i, ot in enumerate(zip(ops, txns)): 185 self.session.begin_transaction() 186 ok, txn = ot 187 # print '%d: %s(%d)[%s]' % (i, ok[0], ok[1], txn) 188 op, k = ok 189 190 # print '%d: %s(%d)[%s]' % (i, ok[0], ok[1], txn) 191 if op == 'stop': 192 c.set_key(k) 193 self.session.truncate(None, None, c, None) 194 kstart = 1 195 kstop = k 196 elif op == 'start': 197 c.set_key(k) 198 self.session.truncate(None, c, None, None) 199 kstart = k 200 kstop = len(current) 201 elif op == 'both': 202 c2 = self.session.open_cursor(self.uri, None) 203 # For both, the key given is the start key. Add 2 204 # for the stop key. 205 kstart = k 206 kstop = k + 2 207 c.set_key(kstart) 208 c2.set_key(kstop) 209 self.session.truncate(None, c, c2, None) 210 c2.close() 211 elif op == 'all': 212 c2 = self.session.open_cursor(self.uri, None) 213 kstart = 1 214 kstop = len(current) 215 c.set_key(kstart) 216 c2.set_key(kstop) 217 self.session.truncate(None, c, c2, None) 218 c2.close() 219 220 while (kstart <= kstop): 221 del current[kstart] 222 kstart += 1 223 224 # print current 225 # Check the state after each operation. 226 self.check_all(current, committed) 227 228 if txn == 'commit': 229 committed = current.copy() 230 self.session.commit_transaction() 231 elif txn == 'rollback': 232 current = committed.copy() 233 self.session.rollback_transaction() 234 235 # Check the state after each commit/rollback. 236 self.check_all(current, committed) 237 238 # Check the log state after the entire op completes 239 # and run recovery. 240 if self.scenario_number % (len(test_txn05.scenarios) / 100 + 1) == 0: 241 self.check_log(committed) 242 243if __name__ == '__main__': 244 wttest.run() 245