github.com/percona/percona-xtradb-cluster-operator@v1.14.0/e2e-tests/pitr-gap-errors/run (about)

     1  #!/bin/bash
     2  
     3  set -o errexit
     4  
     5  test_dir=$(realpath $(dirname $0))
     6  . "${test_dir}/../functions"
     7  
     8  set_debug
     9  
    10  GTID_PATTERN='[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}:[0-9]+'
    11  
    12  if [[ $IMAGE_PXC =~ 5\.7 ]]; then
    13  	echo "Skipping PITR test because 5.7 doesn't support it!"
    14  	exit 0
    15  fi
    16  
    17  write_test_data() {
    18  	local cluster=$1
    19  	local config=$2
    20  	local size="${3:-3}"
    21  	local sleep="${4:-10}"
    22  	local secretsFile="${5:-$conf_dir/secrets.yml}"
    23  	local pxcClientFile="${6:-$conf_dir/client.yml}"
    24  
    25  	local proxy=$(get_proxy "$cluster")
    26  
    27  	desc 'write test data'
    28  	if [[ $IMAGE_PXC =~ 5\.7 ]] && [[ "$(is_keyring_plugin_in_use "$cluster")" ]]; then
    29  		encrypt='ENCRYPTION=\"Y\"'
    30  	fi
    31  	run_mysql \
    32  		"CREATE DATABASE IF NOT EXISTS test; use test; CREATE TABLE IF NOT EXISTS test (id int PRIMARY KEY) $encrypt;" \
    33  		"-h $proxy -uroot -proot_password"
    34  	run_mysql \
    35  		'INSERT test.test (id) VALUES (100500); INSERT test.test (id) VALUES (100501); INSERT test.test (id) VALUES (100502);' \
    36  		"-h $proxy -uroot -proot_password"
    37  	sleep 30
    38  	for i in $(seq 0 $((size - 1))); do
    39  		compare_mysql_cmd "select-3" "SELECT * from test.test;" "-h $cluster-pxc-$i.$cluster-pxc -uroot -proot_password"
    40  	done
    41  
    42  	if [ "$(is_keyring_plugin_in_use "$cluster")" ]; then
    43  		table_must_be_encrypted "$cluster" "test"
    44  	fi
    45  }
    46  
    47  write_data_for_pitr() {
    48  	local cluster=$1
    49  	local proxy=$(get_proxy "$cluster")
    50  
    51  	desc "write data for pitr"
    52  	run_mysql \
    53  		'INSERT test.test (id) VALUES (100503); INSERT test.test (id) VALUES (100504); INSERT test.test (id) VALUES (100505);' \
    54  		"-h $proxy -uroot -proot_password"
    55  }
    56  
    57  write_more_data() {
    58  	local cluster=$1
    59  	local proxy=$(get_proxy "$cluster")
    60  	desc "write extra data"
    61  	run_mysql \
    62  		'INSERT test.test (id) VALUES (100506); INSERT test.test (id) VALUES (100507); INSERT test.test (id) VALUES (100508); INSERT test.test (id) VALUES (100509); INSERT test.test (id) VALUES (100510);' \
    63  		"-h $proxy -uroot -proot_password"
    64  }
    65  
    66  create_binlog_gap() {
    67  	desc 'create binlog gap'
    68  
    69  	kubectl patch pxc $cluster --type=merge -p '{"spec":{"backup":{"pitr":{"enabled":false}}}}'
    70  	sleep 5 # wait for pitr pod to shutdown
    71  	# write data which will be lost
    72  	run_mysql \
    73  		'INSERT test.gap (id) VALUES (100800); INSERT test.gap (id) VALUES (100801); INSERT test.gap (id) VALUES (100802);' \
    74  		"-h $proxy -uroot -proot_password"
    75  	# flush binlogs
    76  	run_mysql_local 'FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-0"
    77  	run_mysql_local 'FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-1"
    78  	run_mysql_local 'FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-2"
    79  	# purge binlogs
    80  	run_mysql_local 'PURGE BINARY LOGS BEFORE now();' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-0"
    81  	run_mysql_local 'PURGE BINARY LOGS BEFORE now();' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-1"
    82  	run_mysql_local 'PURGE BINARY LOGS BEFORE now();' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-2"
    83  	# re-enable pitr
    84  	kubectl patch pxc $cluster --type=merge -p '{"spec":{"backup":{"pitr":{"enabled":true}}}}'
    85  	# flush binlogs to trigger binlog collector
    86  	# data below will be force recovered
    87  	run_mysql \
    88  		'INSERT test.gap (id) VALUES (100803); INSERT test.gap (id) VALUES (100804); INSERT test.gap (id) VALUES (100805);' \
    89  		"-h $cluster-proxysql -uroot -proot_password"
    90  	run_mysql_local 'FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-0"
    91  	run_mysql_local 'FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-1"
    92  	run_mysql_local 'FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-2"
    93  	sleep 65 # wait for next PITR collect cycle and error to appear
    94  }
    95  
    96  check_binlog_gap_error() {
    97  	desc 'check binlog gap error'
    98  
    99  	# check error in pitr log
   100  	local err_text1=$(kubectl_bin logs $(get_pitr_pod) | grep -c "ERROR: Couldn't find the binlog that contains GTID set")
   101  	local err_text2=$(kubectl_bin logs $(get_pitr_pod) | grep -c "ERROR: Gap detected in the binary logs. Binary logs will be uploaded anyway, but full backup needed for consistent recovery.")
   102  	if [[ $err_text1 -eq 0 || $err_text2 -eq 0 ]]; then
   103  		echo "ERROR: Gap error text is not found in PITR pod logs."
   104  		exit 1
   105  	fi
   106  	# check error in operator log
   107  	local err_text3=$(kubectl_bin logs ${OPERATOR_NS:+-n$OPERATOR_NS} $(get_operator_pod) | grep -c "Gap detected in binary logs")
   108  	if [[ $err_text3 -eq 0 ]]; then
   109  		echo "ERROR: Gap error text is not found in operator pod logs."
   110  		exit 1
   111  	fi
   112  	# check backup on-pitr-minio-gap marked as unready for PITR restore
   113  	local backup_cond=$(kubectl_bin get pxc-backup on-pitr-minio-gap -ojsonpath='{.status.conditions[]}' | grep -c '"reason":"BinlogGapDetected","status":"False","type":"PITRReady"')
   114  	if [[ $backup_cond -eq 0 ]]; then
   115  		echo "ERROR: Backup is not tagged as PITR unready in the backup condition."
   116  		kubectl_bin get pxc-backup on-pitr-minio-gap -oyaml
   117  		exit 1
   118  	fi
   119  }
   120  
   121  check_binlog_gap_restore() {
   122  	local type=$1
   123  
   124  	desc 'check binlog gap restore: ' $type
   125  	# disable pitr
   126  	kubectl patch pxc $cluster --type=merge -p '{"spec":{"backup":{"pitr":{"enabled":false}}}}'
   127  	# try restore, check error
   128  	if [ "$type" == "error" ]; then
   129  		kubectl_bin apply -f $test_dir/conf/restore-on-pitr-minio-gap-error.yaml
   130  		wait_backup_restore "on-pitr-minio-gap-error" "Failed"
   131  		local backup_error=$(kubectl_bin get pxc-restore on-pitr-minio-gap-error -ojsonpath='{.status.comments}' | grep -c "Backup doesn't guarantee consistent recovery with PITR. Annotate PerconaXtraDBClusterRestore with percona.com/unsafe-pitr to force it.")
   132  		if [[ $backup_error -eq 0 ]]; then
   133  			echo "ERROR: Backup is not tagged as PITR unready in the backup condition."
   134  			kubectl_bin get pxc-backup on-pitr-minio-gap -oyaml
   135  			exit 1
   136  		fi
   137  		kubectl_bin delete -f "$test_dir/conf/restore-on-pitr-minio-gap-error.yaml"
   138  	elif [ "$type" == "force" ]; then
   139  		kubectl_bin apply -f "$test_dir/conf/restore-on-pitr-minio-gap-force.yaml"
   140  		wait_backup_restore "on-pitr-minio-gap-force" "Succeeded"
   141  		wait_for_running "$cluster-proxysql" 2
   142  		wait_for_running "$cluster-pxc" 3
   143  		wait_cluster_consistency "$cluster" 3 2
   144  		kubectl_bin logs job/restore-job-on-pitr-minio-gap-force-${cluster}
   145  		compare_mysql_cmd "select-gap" "SELECT * from test.gap;" "-h $cluster-pxc-0.$cluster-pxc -uroot -proot_password"
   146  		compare_mysql_cmd "select-gap" "SELECT * from test.gap;" "-h $cluster-pxc-1.$cluster-pxc -uroot -proot_password"
   147  		compare_mysql_cmd "select-gap" "SELECT * from test.gap;" "-h $cluster-pxc-2.$cluster-pxc -uroot -proot_password"
   148  		kubectl_bin delete -f "$test_dir/conf/restore-on-pitr-minio-gap-force.yaml"
   149  	else
   150  		echo "Wrong restore type!"
   151  		exit 1
   152  	fi
   153  }
   154  
   155  check_invalid_binlogs_error() {
   156  	local binlog=$1
   157  	desc 'check invalid binlogs'
   158  
   159  	# check error in pitr log
   160  	local err_text
   161  	err_text=$(kubectl_bin logs "$(get_pitr_pod)" | grep -c "ERROR: Binlog file $binlog is invalid")
   162  	if [[ $err_text -eq 0 ]]; then
   163  		echo "ERROR: Invalid binlog error text is not found in PITR pod logs."
   164  		exit 1
   165  	fi
   166  
   167  	sleep 65 # wait for next PITR collect cycle and error to appear
   168  }
   169  
   170  find_invalid_binlog_name() {
   171  	local pod=$1
   172  	local offset=$2
   173  
   174  	binlogs=()
   175  	while IFS= read -r line; do
   176  		binlogs+=("$line")
   177  	done < <(run_mysql_local "SHOW BINARY LOGS" "-h127.0.0.1 -P3306 -uroot -proot_password" "$pod" | awk '{print $1}')
   178  
   179  	for ((i = 0; i < ${#binlogs[@]}; i++)); do
   180  		local events
   181  		events=$(run_mysql_local "SHOW BINLOG EVENTS IN '${binlogs[$i]}';" "-h127.0.0.1 -P3306 -uroot -proot_password" "$pod")
   182  		if echo "$events" | grep -q "CREATE TABLE IF NOT EXISTS invalid"; then
   183  			echo "${binlogs[$((i + offset))]}"
   184  			return
   185  		fi
   186  	done
   187  
   188  	echo "No invalid binlog found"
   189  	exit 1
   190  }
   191  
   192  gtidset_to_gtid() {
   193  	local gtidset=$1
   194  	local gtid
   195  	# Split the input string into two parts using ":" as the delimiter
   196  	IFS=':' read -ra parts <<<"$gtidset"
   197  
   198  	# Get the number after the "-" symbol
   199  	number_part="${parts[1]#*-}"
   200  
   201  	# Create the final transformed string
   202  	transformed_string="${parts[0]}:$number_part"
   203  
   204  	echo "$transformed_string"
   205  }
   206  
   207  invalid_binlog_test() {
   208  	desc 'start invalid binlog test'
   209  
   210  	local proxy
   211  	proxy="$(get_proxy "$cluster")"
   212  
   213  	kubectl patch pxc $cluster --type=merge -p '{"spec":{"backup":{"pitr":{"enabled":false}}}}'
   214  	sleep 5 # wait for pitr pod to shutdown
   215  
   216  	# restore to initial state
   217  	local backup
   218  	backup="on-pitr-minio"
   219  	yq "$test_dir/conf/restore-on-pitr-minio.yaml" \
   220  		| yq eval 'del(.spec.pitr)' \
   221  		| yq eval 'del(.spec.backupSource)' \
   222  		| yq eval ".spec.backupName=\"$backup\"" \
   223  		| kubectl_bin apply -f -
   224  	wait_backup_restore ${backup}
   225  	wait_for_running "$cluster-proxysql" 2
   226  	wait_for_running "$cluster-pxc" 3
   227  	wait_cluster_consistency "$cluster" 3 2
   228  
   229  	run_mysql \
   230  		"CREATE DATABASE IF NOT EXISTS test; use test; " \
   231  		"-h $proxy -uroot -proot_password"
   232  	write_test_data "$cluster"
   233  	write_data_for_pitr "$cluster"
   234  
   235  	# we need to use this function in test
   236  	run_mysql_local "CREATE FUNCTION get_binlog_by_gtid_set RETURNS STRING SONAME 'binlog_utils_udf.so';" \
   237  		"-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-0"
   238  	run_mysql_local "CREATE FUNCTION get_binlog_by_gtid_set RETURNS STRING SONAME 'binlog_utils_udf.so';" \
   239  		"-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-1"
   240  	run_mysql_local "CREATE FUNCTION get_binlog_by_gtid_set RETURNS STRING SONAME 'binlog_utils_udf.so';" \
   241  		"-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-2"
   242  	# flush binlogs
   243  	run_mysql_local 'FLUSH BINARY LOGS;FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-0"
   244  	run_mysql_local 'FLUSH BINARY LOGS;FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-1"
   245  	run_mysql_local 'FLUSH BINARY LOGS;FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-2"
   246  
   247  	# this data should be in new binlog
   248  	run_mysql \
   249  		"USE test; CREATE TABLE IF NOT EXISTS invalid (id int PRIMARY KEY);" \
   250  		"-h $proxy -uroot -proot_password"
   251  	run_mysql \
   252  		'INSERT test.invalid (id) VALUES (100900);
   253  		 INSERT test.invalid (id) VALUES (100901);
   254  		 INSERT test.invalid (id) VALUES (100902);' \
   255  		"-h $proxy -uroot -proot_password"
   256  
   257  	# flush binlogs
   258  	run_mysql_local 'FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-0"
   259  	run_mysql_local 'FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-1"
   260  	run_mysql_local 'FLUSH BINARY LOGS;' "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-2"
   261  
   262  	write_more_data "$cluster" # this data should be in new binlog
   263  
   264  	invalid_binlog=$(find_invalid_binlog_name "$cluster-pxc-0")
   265  	# get gtidset of invalid binlog
   266  	gtidset=$(run_mysql_local "SELECT get_gtid_set_by_binlog('$invalid_binlog');" "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-0")
   267  
   268  	next_binlog=$(find_invalid_binlog_name "$cluster-pxc-0" 1)
   269  	next_gtidset=$(run_mysql_local "SELECT get_gtid_set_by_binlog('$next_binlog');" "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-0")
   270  	next_gtid=$(gtidset_to_gtid "$next_gtidset")
   271  
   272  	# we should make binlogs with this gtidset empty
   273  	local binlog
   274  	binlog="$invalid_binlog"
   275  	kubectl exec $cluster-pxc-0 -- bash -c "echo \"\" > /var/lib/mysql/$binlog"
   276  	binlog=$(run_mysql_local "SELECT get_binlog_by_gtid_set('$gtidset');" "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-1")
   277  	kubectl exec $cluster-pxc-1 -- bash -c "echo \"\" > /var/lib/mysql/$binlog"
   278  	binlog=$(run_mysql_local "SELECT get_binlog_by_gtid_set('$gtidset');" "-h127.0.0.1 -P3306 -uroot -proot_password" "$cluster-pxc-2")
   279  	kubectl exec $cluster-pxc-2 -- bash -c "echo \"\" > /var/lib/mysql/$binlog"
   280  
   281  	sleep 20
   282  
   283  	kubectl patch pxc $cluster --type=merge -p '{"spec": {"backup": {"storages": {"minio-binlogs": {"s3": {"bucket": "operator-testing/binlogs-invalid"}}}}}}'
   284  	# re-enable pitr
   285  	kubectl patch pxc $cluster --type=merge -p '{"spec":{"backup":{"pitr":{"enabled":true}}}}'
   286  	sleep 180 # wait for next PITR collect cycle and error to appear
   287  	check_invalid_binlogs_error "$invalid_binlog"
   288  
   289  	gtid="$next_gtid"
   290  
   291  	if [[ ! ${gtid} =~ ${GTID_PATTERN} ]]; then
   292  		printf "Some garbage --> %s <-- instead of legit GTID. Exiting ${gtid}"
   293  		exit 1
   294  	fi
   295  
   296  	run_recovery_check_pitr "$cluster" "restore-on-pitr-minio-invalid" "on-pitr-minio" "select-6" "" "" "$gtid"
   297  
   298  	# invalid table should not exist
   299  	compare_mysql_cmd "select-invalid" "SELECT * from test.invalid;" "-h $cluster-pxc-0.$cluster-pxc -uroot -proot_password"
   300  	compare_mysql_cmd "select-invalid" "SELECT * from test.invalid;" "-h $cluster-pxc-1.$cluster-pxc -uroot -proot_password"
   301  	compare_mysql_cmd "select-invalid" "SELECT * from test.invalid;" "-h $cluster-pxc-2.$cluster-pxc -uroot -proot_password"
   302  
   303  	desc 'done test invalid binlogs'
   304  }
   305  
   306  main() {
   307  	create_infra $namespace
   308  	deploy_cert_manager
   309  	kubectl_bin apply -f "$test_dir/conf/issuer.yml"
   310  	kubectl_bin apply -f "$test_dir/conf/cert.yml"
   311  	sleep 25
   312  	# We are using minio with tls enabled to check if `verifyTLS: false` works fine
   313  	start_minio "tls-minio"
   314  
   315  	cluster="pitr-gap-errors"
   316  	spinup_pxc "$cluster" "$test_dir/conf/$cluster.yml"
   317  
   318  	run_backup "$cluster" "on-pitr-minio"
   319  
   320  	write_test_data "$cluster"
   321  
   322  	desc 'show binlog events'
   323  	proxy=$(get_proxy "$cluster")
   324  	run_mysql "SHOW BINLOG EVENTS IN 'binlog.000005';" "-h ${proxy} -uroot -proot_password"
   325  	run_mysql "SHOW BINLOG EVENTS IN 'binlog.000006';" "-h ${proxy} -uroot -proot_password"
   326  
   327  	write_data_for_pitr "$cluster"
   328  	sleep 120 # need to wait while collector catch new data
   329  
   330  	desc 'check second backup/restore data from binlogs'
   331  	run_backup "$cluster" "on-pitr-minio1"
   332  	write_more_data "$cluster"
   333  	dest=$(sed 's,/,\\/,g' <<<$(kubectl get pxc-backup on-pitr-minio1 -o jsonpath='{.status.destination}'))
   334  	sleep 80 # need to wait while collector catch new data
   335  	run_recovery_check_pitr "$cluster" "restore-on-pitr-minio1" "on-pitr-minio1" "select-5" "" "$dest" ""
   336  
   337  	desc 'binlog gap test'
   338  	desc 'create binlog gap backup (will be marked as PITR unready)'
   339  	run_mysql \
   340  		"CREATE DATABASE IF NOT EXISTS test; use test; CREATE TABLE IF NOT EXISTS gap (id int PRIMARY KEY);" \
   341  		"-h $proxy -uroot -proot_password"
   342  	run_backup "$cluster" "on-pitr-minio-gap"
   343  	create_binlog_gap
   344  	check_binlog_gap_error
   345  	check_binlog_gap_restore "error"
   346  	check_binlog_gap_restore "force"
   347  	desc "done binlog gap test"
   348  
   349  	invalid_binlog_test
   350  
   351  	destroy $namespace
   352  	desc "test passed"
   353  }
   354  
   355  main