#!/usr/bin/env python # # Public Domain 2014-present MongoDB, Inc. # Public Domain 2008-2014 WiredTiger, Inc. # # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # test_compat01.py # Check compatibility API import fnmatch, os import wiredtiger, wttest from suite_subprocess import suite_subprocess from wtdataset import SimpleDataSet, simple_key from wtscenario import make_scenarios class test_compat01(wttest.WiredTigerTestCase, suite_subprocess): # Add enough entries and use a small log size to generate more than # one log file. entries = 2000 logmax = "100K" tablename = 'test_compat01' uri = 'table:' + tablename # Declare the log versions that do and do not have prevlsn. # Log version 1 does not have the prevlsn record. # Log version 2 introduced that record. # Log versions 3 and higher continue to have that record. min_logv = 2 latest_logv = 5 # The API uses only the major and minor numbers but accepts with # and without the patch number. Test both. start_compat = [ ('def', dict(compat1='none', logv1=5)), ('100', dict(compat1='10.0', logv1=5)), ('33', dict(compat1='3.3', logv1=4)), ('32', dict(compat1='3.2', logv1=3)), ('31', dict(compat1="3.1", logv1=3)), ('30', dict(compat1="3.0", logv1=2)), ('30_patch', dict(compat1="3.0.0", logv1=2)), ('26', dict(compat1="2.6", logv1=1)), ('old', dict(compat1="1.8", logv1=1)), ('old_patch', dict(compat1="1.8.1", logv1=1)), ] restart_compat = [ ('def2', dict(compat2='none', logv2=5)), ('100_2', dict(compat2='10.0', logv2=5)), ('33_2', dict(compat2='3.3', logv2=4)), ('32_2', dict(compat2='3.2', logv2=3)), ('31_2', dict(compat2="3.1", logv2=3)), ('30_2', dict(compat2="3.0", logv2=2)), ('30_patch2', dict(compat2="3.0.0", logv2=2)), ('26_2', dict(compat2="2.6", logv2=1)), ('old2', dict(compat2="1.8", logv2=1)), ('old_patch2', dict(compat2="1.8.1", logv2=1)), ] scenarios = make_scenarios(restart_compat, start_compat) def make_compat_str(self, create): compat_str = '' if (create == True and self.compat1 != 'none'): #compat_str = 'verbose=(temporary),compatibility=(release="%s"),' % self.compat1 compat_str = 'compatibility=(release="%s"),' % self.compat1 elif (create == False and self.compat2 != 'none'): #compat_str = 'verbose=(temporary),compatibility=(release="%s"),' % self.compat2 compat_str = 'compatibility=(release="%s"),' % self.compat2 return compat_str def conn_config(self): # Set archive false on the home directory. log_str = 'log=(archive=false,enabled,file_max=%s),' % self.logmax compat_str = self.make_compat_str(True) self.pr("Conn config:" + log_str + compat_str) return log_str + compat_str def check_prev_lsn(self, exists, conn_close): # # Run printlog and look for the prev_lsn log record. Verify its # existence with the passed in expected result. We don't use # check_file_contains because that only looks in the first 100K and # we don't know how big our text-based log output is. Look through # the entire file if needed and set a boolean for comparison. # self.runWt(['printlog'], outfilename='printlog.out', closeconn=conn_close) contains = False with open('printlog.out') as logfile: for line in logfile: if 'optype' in line and 'prev_lsn' in line: contains = True break self.assertEqual(exists, contains) def check_log(self, reconfig): orig_logs = fnmatch.filter(os.listdir('.'), "*gerLog*") compat_str = self.make_compat_str(False) if not reconfig: # # Close and open the connection to force recovery and log archiving # even if archive is turned off (in some circumstances). If we are # downgrading we must archive newer logs. Verify the log files # have or have not been archived. # exist = True if self.logv1 < self.min_logv: exist = False self.check_prev_lsn(exist, True) self.conn.close() log_str = 'log=(enabled,file_max=%s,archive=false),' % self.logmax restart_config = log_str + compat_str self.pr("Restart conn " + restart_config) # # Open a connection to force it to run recovery. # conn = self.wiredtiger_open('.', restart_config) conn.close() check_close = False else: self.pr("Reconfigure: " + compat_str) self.conn.reconfigure(compat_str) check_close = True # # Archiving is turned off explicitly. # # Check logs. The original logs should have been archived only if # we downgraded. In all other cases the original logs should be there. # Downgrade means not running the latest possible log version, not # the difference between original and current. cur_logs = fnmatch.filter(os.listdir('.'), "*Log*") log_present = True if self.logv1 != self.logv2 and self.logv2 != self.latest_logv: log_present = False for o in orig_logs: self.assertEqual(log_present, o in cur_logs) # Run printlog and verify the new record does or does not exist. exist = True if self.logv2 < self.min_logv: exist = False self.check_prev_lsn(exist, check_close) def run_test(self, reconfig): # If reconfiguring with the empty string there is nothing to do. if reconfig == True and self.compat2 == 'none': return self.session.create(self.uri, 'key_format=i,value_format=i') c = self.session.open_cursor(self.uri, None) # # Add some entries to generate log files. # for i in range(self.entries): c[i] = i + 1 c.close() # Check the log state after the entire op completes # and run recovery with the restart compatibility mode. self.check_log(reconfig) # Run the same test but reset the compatibility via # reconfigure or changing it when reopening the connection. def test_reconfig(self): self.run_test(True) def test_restart(self): self.run_test(False) class test_reconfig_fail(wttest.WiredTigerTestCase): # Test an active transaction blocks upgrade/downgrade configuration. def test_reconfig_fail(self): uri = 'table:reconfig_fail' ds = SimpleDataSet(self, uri, 100, key_format='S') ds.populate() # Reconfigure to an older version. compat_str = 'compatibility=(release="2.6")' self.conn.reconfigure(compat_str) self.session.begin_transaction("isolation=snapshot") c = self.session.open_cursor(uri, None) c.set_key(ds.key(20)) c.set_value("abcde") self.assertEquals(c.update(), 0) # Make sure we can reconfigure unrelated things while downgraded # and we have an active transaction. self.conn.reconfigure("cache_size=100M") compat_str = 'compatibility=(release="3.0.0")' msg = '/system must be quiescent/' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda:self.conn.reconfigure(compat_str), msg) if __name__ == '__main__': wttest.run()