1# See the file LICENSE for redistribution information.
2#
3# Copyright (c) 2009 Oracle.  All rights reserved.
4#
5# TEST	rep088
6# TEST  Replication roll-back preserves checkpoint.
7# TEST
8# TEST  Create a situation where a client has to roll back its
9# TEST  log, discarding some existing transactions, in order to sync
10# TEST  with a new master.
11# TEST
12# TEST  1. When the client still has its entire log file history, all
13# TEST     the way back to log file #1, it's OK if the roll-back discards
14# TEST     any/all checkpoints.
15# TEST  2. When old log files have been archived, if the roll-back would
16# TEST     remove all existing checkpoints it must be forbidden.  The log
17# TEST     must always have a checkpoint (or all files back through #1).
18# TEST     The client must do internal init or return JOIN_FAILURE.
19# TEST  3. (the normal case) Old log files archived, and a checkpoint
20# TEST     still exists in the portion of the log which will remain after
21# TEST     the roll-back: no internal-init/JOIN_FAILURE necessary.
22#
23# TODO: maybe just reject anything that doesn't comply with my simplified
24# rep_test clone, like fixed-length record methods, etc.
25
26proc rep088 { method { niter 20 } { tnum 088 } args } {
27	source ./include.tcl
28
29	if { $is_windows9x_test == 1 } {
30		puts "Skipping replication test on Win 9x platform."
31		return
32	}
33
34	# Run for btree only.
35	if { $checking_valid_methods } {
36		set test_methods { btree }
37		return $test_methods
38	}
39	if { [is_btree $method] == 0 } {
40		puts "\tRep$tnum: Skipping for method $method."
41		return
42	}
43
44	set args [convert_args $method $args]
45
46	puts "Rep$tnum ($method): Replication roll-back preserves checkpoint."
47	# Note: expected result = "sync" means the client should be allowed to
48	# synchronize normally (to the found sync point), without any need for
49	# internal init.
50	#
51	# Case #1.
52	puts "Rep$tnum: Rollback without checkpoint, with log file 1"
53	set archive false
54	set ckpt false
55	set result sync
56	rep088_sub $method $niter $tnum $archive $ckpt $result $args
57
58	# Case #2.(a).
59	#
60	puts "Rep$tnum: Forbid rollback over only chkp: join failure"
61	set archive true
62	set ckpt false
63	set result join_failure
64	rep088_sub $method $niter $tnum $archive $ckpt $result $args
65
66	# Case #2.(b): essentially the same, but allow the internal init to
67	# happen, so that we verify that the subsequent restart with recovery
68	# works fine.  NB: this is the obvious failure case prior to bug fix
69	# #16732.
70	#
71	puts "Rep$tnum: Forbid rollback over only chkp: internal init"
72	set archive true
73	set ckpt false
74	set result internal_init
75	rep088_sub $method $niter $tnum $archive $ckpt $result $args
76
77	# Case #3.
78	puts "Rep$tnum: Rollback with sufficient extra checkpoints"
79	set archive true
80	set ckpt true
81	set result sync
82	rep088_sub $method $niter $tnum $archive $ckpt $result $args
83}
84
85proc rep088_sub { method niter tnum archive ckpt result largs } {
86	source ./include.tcl
87	global testdir
88	global util_path
89	global rep_verbose
90	global verbose_type
91
92	set verbargs ""
93	if { $rep_verbose == 1 } {
94		set verbargs " -verbose {$verbose_type on} "
95	}
96
97	env_cleanup $testdir
98	replsetup $testdir/MSGQUEUEDIR
99
100	file mkdir [set dirA $testdir/A]
101	file mkdir [set dirB $testdir/B]
102	file mkdir [set dirC $testdir/C]
103
104	set pagesize 4096
105	append largs " -pagesize $pagesize "
106	set log_buf [expr $pagesize * 2]
107	set log_max [expr $log_buf * 4]
108
109	puts "\tRep$tnum.a: Create master and two clients"
110	repladd 1
111	set env_A_cmd "berkdb_env -create -txn $verbargs \
112              -log_buffer $log_buf -log_max $log_max \
113	    -errpfx SITE_A -errfile /dev/stderr \
114	    -home $dirA -rep_transport \[list 1 replsend\]"
115	set envs(A) [eval $env_A_cmd -rep_master]
116
117	repladd 2
118	set env_B_cmd "berkdb_env -create -txn $verbargs \
119              -log_buffer $log_buf -log_max $log_max \
120              -errpfx SITE_B -errfile /dev/stderr \
121              -home $dirB -rep_transport \[list 2 replsend\]"
122	set envs(B) [eval $env_B_cmd -rep_client]
123
124	repladd 3
125	set env_C_cmd "berkdb_env -create -txn $verbargs \
126              -log_buffer $log_buf -log_max $log_max \
127              -errpfx SITE_C -errfile /dev/stderr \
128              -home $dirC -rep_transport \[list 3 replsend\]"
129	set envs(C) [eval $env_C_cmd -rep_client]
130
131	set envlist "{$envs(A) 1} {$envs(B) 2} {$envs(C) 3}"
132	process_msgs $envlist
133	$envs(A) test force noarchive_timeout
134
135	# Using small log file size, push into the second log file.
136	#
137	puts "\tRep$tnum.b: Write enough txns to exceed 1 log file"
138	while { [lsn_file [next_expected_lsn $envs(C)]] == 1 } {
139		eval rep088_reptest $method $envs(A) $niter $largs
140		process_msgs $envlist
141	}
142
143	# To make sure everything still works in the normal case, put in a
144	# checkpoint here before writing the transactions that will have to be
145	# rolled back.  Later, when the client sees that it must roll back over
146	# (and discard) the later checkpoint, the fact that this checkpoint is
147	# here will allow it to proceed.
148	#
149	if { $ckpt } {
150		puts "\tRep$tnum.c: put in an 'extra' checkpoint."
151		$envs(A) txn_checkpoint
152		process_msgs $envlist
153	}
154
155	# Turn off client TBM (the one that will become master later).
156	#
157	puts "\tRep$tnum.d: Turn off client B and write more txns"
158	$envs(B) close
159	set envlist "{$envs(A) 1} {$envs(C) 3}"
160
161	# Fill a bit more log, and then write a checkpoint.
162	#
163	eval rep088_reptest $method $envs(A) $niter $largs
164	$envs(A) txn_checkpoint
165	replclear 2
166	process_msgs $envlist
167
168	# At the client under test, archive away the first log file.
169	#
170	if { $archive } {
171		puts "\tRep$tnum.e: Archive log at client C"
172		exec $util_path/db_archive -d -h $dirC
173	}
174
175	# Maybe another cycle of filling and checkpoint.
176	#
177	eval rep088_reptest $method $envs(A) $niter $largs
178	$envs(A) txn_checkpoint
179	replclear 2
180	process_msgs $envlist
181
182	# Now turn off the master, and turn on the TBM site as master.  The
183	# client under test has to sync with the new master.  Just to make sure
184	# I understand what's going on, turn off auto-init.
185	#
186
187	if { $result != "internal_init" } {
188		$envs(C) rep_config {noautoinit on}
189	}
190	puts "\tRep$tnum.f: Switch master to site B, try to sync client C"
191	$envs(A) close
192	set envs(B) [eval $env_B_cmd -rep_master]
193	set envlist "{$envs(B) 2} {$envs(C) 3}"
194	replclear 1
195	set succeeded [catch { process_msgs $envlist } ret]
196
197	switch $result {
198		internal_init {
199			error_check_good inited $succeeded 0
200
201			# Now stop the client, and try restarting it with
202			# recovery.
203			#
204			$envs(C) close
205			set envs(C) [eval $env_C_cmd -rep_client -recover]
206		}
207		join_failure {
208			error_check_bad no_autoinit $succeeded 0
209			error_check_good join_fail \
210			    [is_substr $ret DB_REP_JOIN_FAILURE] 1
211		}
212		sync {
213			error_check_good sync_ok $succeeded 0
214			error_check_good not_outdated \
215			    [stat_field $envs(C) rep_stat \
216				 "Outdated conditions"] 0
217		}
218		default {
219			error "FAIL: unknown test result option $result"
220		}
221	}
222
223	$envs(C) close
224	$envs(B) close
225	replclose $testdir/MSGQUEUEDIR
226}
227
228# A simplified clone of proc rep_test, with the crucial distinction that it
229# doesn't do any of its own checkpointing.  For this test we need explicit
230# control of when checkpoints should happen.  This proc doesn't support
231# access methods using record numbers.
232proc rep088_reptest { method env niter args } {
233	source ./include.tcl
234
235	set omethod [convert_method $method]
236	set largs [convert_args $method $args]
237	set db [eval berkdb_open_noerr -env $env -auto_commit \
238		    -create $omethod $largs test.db]
239
240	set did [open $dict]
241	for { set i 0 } { $i < $niter && [gets $did str] >= 0 } { incr i } {
242		set key $str
243		set str [reverse $str]
244		$db put $key $str
245	}
246	close $did
247	$db close
248}
249