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