diff options
Diffstat (limited to 'test/rep075.tcl')
-rw-r--r-- | test/rep075.tcl | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/test/rep075.tcl b/test/rep075.tcl new file mode 100644 index 0000000..d9119d3 --- /dev/null +++ b/test/rep075.tcl @@ -0,0 +1,551 @@ +# See the file LICENSE for redistribution information. +# +# Copyright (c) 2001-2009 Oracle. All rights reserved. +# +# $Id$ +# +# TEST rep075 +# TEST Replication and prepared transactions. +# TEST Test having outstanding prepared transactions and simulating +# TEST crashing or upgrading or downgrading sites. +# TEST +# +proc rep075 { method { tnum "075" } args } { + + source ./include.tcl + global databases_in_memory + global mixed_mode_logging + global repfiles_in_memory + + if { $is_windows9x_test == 1 } { + puts "Skipping replication test on Win 9x platform." + return + } + + # Run for all access methods. + if { $checking_valid_methods } { + set test_methods { btree } + return $test_methods + } + if { [is_btree $method] == 0 } { + puts "Rep075: Skipping for method $method" + return + } + + set args [convert_args $method $args] + set logsets [create_logsets 2] + + # Swapping the envs is the only thing that should + # work for: + # HP, old Windows: can't open two handles on same env. + # in-memory logs: prepared txns don't survive recovery + # NIM databases: can't be recovered + # + if { $is_hp_test == 1 || $is_windows9x_test == 1 || + $mixed_mode_logging > 0 || $databases_in_memory == 1 } { + set prep {swap} + } else { + set prep {dbrecover swap resolve recover envrecover} + } + set ops {commit abort both} + + # Set up for on-disk or in-memory databases. + set msg "using on-disk databases" + if { $databases_in_memory } { + set msg "using named in-memory databases" + + } + set msg2 "and on-disk replication files" + if { $repfiles_in_memory } { + set msg2 "and in-memory replication files" + } + + # Run the body of the test with and without recovery. + foreach l $logsets { + foreach p $prep { + foreach o $ops { + puts "Rep$tnum ($method $p $o):\ + Replication and prepared txns $msg $msg2." + puts "Rep$tnum: Master logs are [lindex $l 0]" + puts "Rep$tnum: Client logs are [lindex $l 1]" + puts "Rep$tnum: close DBs after prepare" + rep075_sub $method $tnum $l $p $o 1 $args + puts "Rep$tnum: close DBs before prepare" + rep075_sub $method $tnum $l $p $o 0 $args + } + } + } +} + +proc rep075_sub { method tnum logset prep op after largs } { + global testdir + global databases_in_memory + global repfiles_in_memory + global rep_verbose + global verbose_type + global util_path + + set verbargs "" + if { $rep_verbose == 1 } { + set verbargs " -verbose {$verbose_type on} " + } + + set repmemargs "" + if { $repfiles_in_memory } { + set repmemargs "-rep_inmem_files " + } + + env_cleanup $testdir + + replsetup $testdir/MSGQUEUEDIR + + set masterdir $testdir/MASTERDIR + set clientdir $testdir/CLIENTDIR + set clientdir2 $testdir/CLIENTDIR2 + file mkdir $masterdir + file mkdir $clientdir + file mkdir $clientdir2 + + # Log size is small so we quickly create more than one. + # The documentation says that the log file must be at least + # four times the size of the in-memory log buffer. + set pagesize 4096 + append largs " -pagesize $pagesize " + set log_buf [expr $pagesize * 2] + set log_max [expr $log_buf * 4] + set m_logargs " -log_buffer $log_buf " + set c_logargs " -log_buffer $log_buf " + + set m_logtype [lindex $logset 0] + set c_logtype [lindex $logset 1] + + # In-memory logs require a large log buffer, and cannot + # be used with -txn nosync. + set m_logargs [adjust_logargs $m_logtype] + set c_logargs [adjust_logargs $c_logtype] + set m_txnargs [adjust_txnargs $m_logtype] + set c_txnargs [adjust_txnargs $c_logtype] + + # Open a master. + repladd 1 + set ma_envcmd "berkdb_env_noerr -create $m_txnargs \ + $repmemargs \ + $m_logargs -errpfx ENV0 -log_max $log_max $verbargs \ + -home $masterdir -rep_transport \[list 1 replsend\]" + set env0 [eval $ma_envcmd -rep_master] + set masterenv $env0 + error_check_good master_env [is_valid_env $env0] TRUE + + # Open a client. + repladd 2 + set cl_envcmd "berkdb_env_noerr -create $c_txnargs \ + $repmemargs \ + $c_logargs -errpfx ENV1 -log_max $log_max $verbargs \ + -home $clientdir -rep_transport \[list 2 replsend\]" + set env1 [eval $cl_envcmd -rep_client] + set clientenv $env1 + error_check_good client_env [is_valid_env $env1] TRUE + + repladd 3 + set cl2_envcmd "berkdb_env_noerr -create $c_txnargs \ + $repmemargs \ + $c_logargs -errpfx ENV2 -log_max $log_max $verbargs \ + -home $clientdir2 -rep_transport \[list 3 replsend\]" + set env2 [eval $cl2_envcmd -rep_client] + set clientenv2 $env2 + error_check_good client_env [is_valid_env $env2] TRUE + + set omethod [convert_method $method] + + # Bring the clients online by processing the startup messages. + set envlist "{$env0 1} {$env1 2} {$env2 3}" + process_msgs $envlist + + # + # Run rep_test in a database with a sub database, or in a + # named in-memory database. + # + if { $databases_in_memory } { + set testfile { "" "test1.db" } + set testfile2 { "" "test2.db" } + set db1 [eval {berkdb_open_noerr -env $masterenv -auto_commit \ + -create -mode 0644} $largs $omethod $testfile] + } else { + set testfile "test1.db" + set testfile2 "test2.db" + set sub "subdb" + set db1 [eval {berkdb_open_noerr -env $masterenv -auto_commit \ + -create -mode 0644} $largs $omethod $testfile $sub] + } + error_check_good dbopen [is_valid_db $db1] TRUE + + puts "\tRep$tnum.a: Running rep_test in replicated env." + set niter 1 + eval rep_test $method $masterenv $db1 $niter 0 0 0 $largs + process_msgs $envlist + + set db [eval {berkdb_open_noerr -env $masterenv -auto_commit \ + -create -mode 0644} $largs $omethod $testfile2] + error_check_good dbopen [is_valid_db $db] TRUE + + # + # Create and prepare 2 transactions: + # One txn is for the first database and one txn for the + # second database. We want to test that we can detect + # when the last restored txn has been resolved. And we + # want to test various files being open. + # + puts "\tRep$tnum.b: Prepare some txns." + set pbnyc 2 + set key key + set data some_data + set txn1 [$masterenv txn] + error_check_good txn [is_valid_txn $txn1 $masterenv] TRUE + error_check_good put [$db1 put -txn $txn1 $key $data] 0 + + set gid [make_gid rep075:$txn1] + error_check_good commit [$txn1 prepare $gid] 0 + + set txn2 [$masterenv txn] + error_check_good txn [is_valid_txn $txn2 $masterenv] TRUE + error_check_good put [$db put -txn $txn2 $key $data] 0 + + set gid [make_gid rep075:$txn2] + error_check_good commit [$txn2 prepare $gid] 0 + if { $after == 0 } { + $db1 close + $db close + } + process_msgs $envlist + + # + # Now we have txns on a master that are PBNYC (prepared but + # not yet committed). Alter the replication system now + # based on what we're testing this time through. + # + puts "\tRep$tnum.c: Reset replication ($prep)." + + if { $op == "commit" } { + set op1 commit + set op2 commit + } elseif { $op == "abort" } { + set op1 abort + set op2 abort + } else { + set i [berkdb random_int 0 1] + if { $i == 0 } { + set op1 commit + set op2 abort + } else { + set op1 abort + set op2 commit + } + } + set oplist [list $op1 $op2] + # + # If we are doing a swap, swap roles between master and client + # and then call txn recover. Master should then commit. + # This operation tests handling prepared txns in replication code. + # + # If we are doing a recover, each site stops using its old + # env handle and then opens a new one, with recovery. + # This operation tests handling prepared txns and then + # starting replication. + # + # If we are doing an envrecover, each site stops using its old + # env handle and then opens a new one, with recovery. + # Each site then opens a 2nd dbenv handle to run txn_recover + # and resolve each operation. + # This operation tests handling prepared txns and then + # starting replication. + # + # If we are doing a resolve, each site prepares the txns + # and then resolves the txns and then stops using the old + # env handle to cause a "crash". We then open a new one + # with recovery. This operation tests handling prepared + # txns and having them resolved. + # + if { $prep == "swap" } { + puts "\tRep$tnum.c.0: Swap roles master->client." + # + # A downgrading master must resolve the txns. So, commit + # them here, but don't send the messages to the client that + # is about to become master. + # + error_check_good commit [$txn1 commit] 0 + error_check_good commit [$txn2 commit] 0 + if { $after == 1 } { + $db1 close + $db close + } + replclear 2 + replclear 3 + set newclient $env0 + error_check_good downgrade [$newclient rep_start -client] 0 + set ctxnlist [$newclient txn_recover] + set newmaster $env1 + puts "\tRep$tnum.c.1: Swap roles client->master." + error_check_good upgrade [$newmaster rep_start -master] 0 + set txnlist [$newmaster txn_recover] + + puts "\tRep$tnum.c.2: Check status of prepared txn." + error_check_good txnlist_len [llength $txnlist] $pbnyc + error_check_good txnlist_len [llength $ctxnlist] 0 + + # + # Now commit that old prepared txn. + # + puts "\tRep$tnum.c.3: Resolve prepared txn ($op)." + rep075_resolve $txnlist $oplist + } elseif { $prep == "recover" } { + # + # To simulate a crash, simply stop using the old handles + # and reopen new ones, with recovery. First flush both + # the log and mpool to disk. + # + set origenv0 $env0 + set origenv1 $env1 + set origtxn1 $txn1 + set origtxn2 $txn2 + puts "\tRep$tnum.c.0: Sync and recover master environment." + error_check_good flush1 [$env0 log_flush] 0 + error_check_good sync1 [$env0 mpool_sync] 0 + if { $after == 1 } { + $db1 close + $db close + } + set env0 [eval $ma_envcmd -recover] + error_check_good master_env [is_valid_env $env0] TRUE + puts "\tRep$tnum.c.1: Run txn_recover on master env." + set txnlist [$env0 txn_recover] + error_check_good txnlist_len [llength $txnlist] $pbnyc + puts "\tRep$tnum.c.2: Resolve txn ($op) on master env." + rep075_resolve $txnlist $oplist + + puts "\tRep$tnum.c.3: Sync and recover client environment." + error_check_good flush1 [$env1 log_flush] 0 + error_check_good sync1 [$env1 mpool_sync] 0 + set env1 [eval $cl_envcmd -recover] + error_check_good client_env [is_valid_env $env1] TRUE + puts "\tRep$tnum.c.4: Run txn_recover on client env." + set txnlist [$env1 txn_recover] + error_check_good txnlist_len [llength $txnlist] $pbnyc + + puts "\tRep$tnum.c.5: Resolve txn ($op) on client env." + rep075_resolve $txnlist $oplist + + puts "\tRep$tnum.c.6: Restart replication on both envs." + error_check_good master [$env0 rep_start -master] 0 + error_check_good client [$env1 rep_start -client] 0 + set newmaster $env0 + set envlist "{$env0 1} {$env1 2} {$env2 3}" + # + # Clean up old Tcl handles. + # + catch {$origenv0 close} res + catch {$origenv1 close} res + catch {$origtxn1 close} res + catch {$origtxn2 close} res + } elseif { $prep == "resolve" } { + # + # Check having prepared txns in the log, but they are + # also resolved before we "crash". + # To simulate a crash, simply stop using the old handles + # and reopen new ones, with recovery. First flush both + # the log and mpool to disk. + # + set origenv0 $env0 + set origenv1 $env1 + set origdb1 $db1 + set origdb $db + puts "\tRep$tnum.c.0: Resolve ($op1 $op2) and recover master." + error_check_good resolve1 [$txn1 $op1] 0 + error_check_good resolve2 [$txn2 $op2] 0 + error_check_good flush0 [$env0 log_flush] 0 + error_check_good sync0 [$env0 mpool_sync] 0 + process_msgs $envlist + set env0 [eval $ma_envcmd -recover] + error_check_good master_env [is_valid_env $env0] TRUE + puts "\tRep$tnum.c.1: Run txn_recover on master env." + set txnlist [$env0 txn_recover] + error_check_good txnlist_len [llength $txnlist] 0 + + puts "\tRep$tnum.c.2: Sync and recover client environment." + error_check_good flush1 [$env1 log_flush] 0 + error_check_good sync1 [$env1 mpool_sync] 0 + set env1 [eval $cl_envcmd -recover] + error_check_good client_env [is_valid_env $env1] TRUE + puts "\tRep$tnum.c.3: Run txn_recover on client env." + set txnlist [$env1 txn_recover] + error_check_good txnlist_len [llength $txnlist] 0 + + puts "\tRep$tnum.c.4: Restart replication on both envs." + error_check_good master [$env0 rep_start -master] 0 + error_check_good client [$env1 rep_start -client] 0 + set newmaster $env0 + set envlist "{$env0 1} {$env1 2} {$env2 3}" + catch {$origenv0 close} res + catch {$origenv1 close} res + catch {$origdb close} res + catch {$origdb1 close} res + } elseif { $prep == "envrecover" || $prep == "dbrecover" } { + # + # To simulate a crash, simply stop using the old handles + # and reopen new ones, with recovery. First flush both + # the log and mpool to disk. + # + set origenv0 $env0 + set origenv1 $env1 + set origtxn1 $txn1 + set origtxn2 $txn2 + puts "\tRep$tnum.c.0: Sync and recover master environment." + error_check_good flush1 [$env0 log_flush] 0 + error_check_good sync1 [$env0 mpool_sync] 0 + set oldgen [stat_field $env0 rep_stat "Generation number"] + error_check_good flush1 [$env1 log_flush] 0 + error_check_good sync1 [$env1 mpool_sync] 0 + if { $after == 1 } { + $db1 close + $db close + } + if { $prep == "dbrecover" } { + set recargs "-h $masterdir -c " + set stat [catch {eval exec $util_path/db_recover \ + -e $recargs} result] + if { $stat == 1 } { + error "FAIL: Recovery error: $result." + } + set recargs "-h $clientdir -c " + set stat [catch {eval exec $util_path/db_recover \ + -e $recargs} result] + if { $stat == 1 } { + error "FAIL: Recovery error: $result." + } + } + # + # !!! + # We still need to open with recovery, even if 'dbrecover' + # because db_recover cannot open the env with replication + # enabled. But db_recover will be the real recovery that + # needs to deal with the prepared txn. This recovery below + # for db_recover, should be a no-op essentially. + # + set recenv0 [eval $ma_envcmd -recover] + error_check_good master_env [is_valid_env $recenv0] TRUE + puts "\tRep$tnum.c.1: Run txn_recover on master env." + set env0 [eval $ma_envcmd] + error_check_good master_env [is_valid_env $env0] TRUE + set txnlist [$env0 txn_recover] + error_check_good txnlist_len [llength $txnlist] $pbnyc + puts "\tRep$tnum.c.2: Resolve txn ($op) on master env." + rep075_resolve $txnlist $oplist + error_check_good recenv0_close [$recenv0 close] 0 + + puts "\tRep$tnum.c.3: Recover client environment." + set recenv1 [eval $cl_envcmd -recover -errpfx "ENV1REC"] + error_check_good client_env [is_valid_env $recenv1] TRUE + puts "\tRep$tnum.c.4: Run txn_recover on client env." + set env1 [eval $cl_envcmd -errpfx "ENV1NEW"] + error_check_good client_env [is_valid_env $env1] TRUE + set txnlist [$env1 txn_recover] + error_check_good txnlist_len [llength $txnlist] $pbnyc + + puts "\tRep$tnum.c.5: Resolve txns ($oplist) on client env." + rep075_resolve $txnlist $oplist + error_check_good recenv1_close [$recenv1 close] 0 + + puts "\tRep$tnum.c.6: Restart replication on both envs." + if { $prep == "dbrecover" } { + # + # XXX Since we ran db_recover, we lost the rep gen + # and clientenv2 cannot detect the change. Until + # SR 15396 is fixed, we'll fake it by becoming + # master, downgrading and then upgrading again to + # advance the generation number. + # + error_check_good master [$env0 rep_start -master] 0 + error_check_good master [$env0 rep_start -client] 0 + replclear 2 + replclear 3 + } + error_check_good master [$env0 rep_start -master] 0 + set gen [stat_field $env0 rep_stat "Generation number"] + # + # If in-memory rep, restarting environment puts gen back + # to 1, the same as oldgen. envrecover doesn't do the extra + # rep_start, so gen is expected to stay at 1 in this case. + # + if { $repfiles_in_memory != 0 && $prep == "envrecover" } { + error_check_good gen $gen $oldgen + } else { + error_check_bad gen $gen $oldgen + } + error_check_good client [$env1 rep_start -client] 0 + set newmaster $env0 + set envlist "{$env0 1} {$env1 2} {$env2 3}" + process_msgs $envlist + # + # Clean up old Tcl handles. + # + catch {$origenv0 close} res + catch {$origenv1 close} res + catch {$origtxn1 close} res + catch {$origtxn2 close} res + } + # + # Run a standard rep_test creating test.db now. + # + eval rep_test $method $newmaster NULL $niter 0 0 0 $largs + process_msgs $envlist + + # + # Verify whether or not the key exists in the databases both + # on the client and the master. + # + puts "\tRep$tnum.d: Verify prepared data." + foreach e $envlist { + set env [lindex $e 0] + if { $databases_in_memory } { + set db1 [eval {berkdb_open_noerr -env $env\ + -auto_commit -create -mode 0644} $largs\ + $omethod $testfile] + } else { + set db1 [eval {berkdb_open_noerr -env $env\ + -auto_commit -create -mode 0644} $largs\ + $omethod $testfile $sub] + } + error_check_good dbopen [is_valid_db $db1] TRUE + set db2 [eval {berkdb_open_noerr -env $env -auto_commit \ + -create -mode 0644} $largs $omethod $testfile2] + error_check_good dbopen [is_valid_db $db2] TRUE + set k1 [$db1 get $key] + set k2 [$db2 get $key] + if { $op1 == "commit" } { + error_check_good key [llength $k1] 1 + } else { + error_check_good key [llength $k1] 0 + } + if { $op2 == "commit" } { + error_check_good key [llength $k2] 1 + } else { + error_check_good key [llength $k2] 0 + } + + error_check_good db_close [$db1 close] 0 + error_check_good db_close [$db2 close] 0 + } + error_check_good env0_close [$env0 close] 0 + error_check_good env1_close [$env1 close] 0 + error_check_good env2_close [$env2 close] 0 + + replclose $testdir/MSGQUEUEDIR + return +} + +proc rep075_resolve { txnlist ops } { + error_check_good resolve_lists [llength $txnlist] [llength $ops] + foreach trec $txnlist op $ops { + set txn [lindex $trec 0] + error_check_good commit [$txn $op] 0 + } +} |