vitess.io/vitess@v0.16.2/go/test/endtoend/backup/pitr/backup_mysqlctld_pitr_test.go (about) 1 /* 2 Copyright 2022 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package mysqlctld 18 19 import ( 20 "context" 21 "fmt" 22 "math/rand" 23 "testing" 24 "time" 25 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 29 "vitess.io/vitess/go/mysql" 30 backup "vitess.io/vitess/go/test/endtoend/backup/vtctlbackup" 31 "vitess.io/vitess/go/test/endtoend/cluster" 32 ) 33 34 func waitForReplica(t *testing.T) { 35 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 36 defer cancel() 37 pMsgs := backup.ReadRowsFromPrimary(t) 38 for { 39 rMsgs := backup.ReadRowsFromReplica(t) 40 if len(pMsgs) == len(rMsgs) { 41 // success 42 return 43 } 44 select { 45 case <-ctx.Done(): 46 assert.FailNow(t, "timeout waiting for replica to catch up") 47 return 48 case <-time.After(time.Second): 49 // 50 } 51 } 52 } 53 54 // TestIncrementalBackupMysqlctld - tests incremental backups using myslctld 55 func TestIncrementalBackupMysqlctld(t *testing.T) { 56 defer cluster.PanicHandler(t) 57 // setup cluster for the testing 58 code, err := backup.LaunchCluster(backup.Mysqlctld, "xbstream", 0, nil) 59 require.NoError(t, err, "setup failed with status code %d", code) 60 defer backup.TearDownCluster() 61 62 backup.InitTestTable(t) 63 64 rowsPerPosition := map[string]int{} 65 backupPositions := []string{} 66 67 recordRowsPerPosition := func(t *testing.T) { 68 pos := backup.GetReplicaPosition(t) 69 msgs := backup.ReadRowsFromReplica(t) 70 if _, ok := rowsPerPosition[pos]; !ok { 71 backupPositions = append(backupPositions, pos) 72 rowsPerPosition[pos] = len(msgs) 73 } 74 } 75 76 var fullBackupPos mysql.Position 77 t.Run("full backup", func(t *testing.T) { 78 backup.InsertRowOnPrimary(t, "before-full-backup") 79 waitForReplica(t) 80 manifest, _ := backup.TestReplicaFullBackup(t) 81 fullBackupPos = manifest.Position 82 require.False(t, fullBackupPos.IsZero()) 83 // 84 msgs := backup.ReadRowsFromReplica(t) 85 pos := mysql.EncodePosition(fullBackupPos) 86 backupPositions = append(backupPositions, pos) 87 rowsPerPosition[pos] = len(msgs) 88 }) 89 90 lastBackupPos := fullBackupPos 91 backup.InsertRowOnPrimary(t, "before-incremental-backups") 92 93 tt := []struct { 94 name string 95 writeBeforeBackup bool 96 fromFullPosition bool 97 autoPosition bool 98 expectError string 99 }{ 100 { 101 name: "first incremental backup", 102 }, 103 { 104 name: "make writes, succeed", 105 writeBeforeBackup: true, 106 }, 107 { 108 name: "fail, no binary logs to backup", 109 expectError: "no binary logs to backup", 110 }, 111 { 112 name: "make writes again, succeed", 113 writeBeforeBackup: true, 114 }, 115 { 116 name: "auto position, succeed", 117 writeBeforeBackup: true, 118 autoPosition: true, 119 }, 120 { 121 name: "fail auto position, no binary logs to backup", 122 autoPosition: true, 123 expectError: "no binary logs to backup", 124 }, 125 { 126 name: "auto position, make writes again, succeed", 127 writeBeforeBackup: true, 128 autoPosition: true, 129 }, 130 { 131 name: "from full backup position", 132 fromFullPosition: true, 133 }, 134 } 135 var fromFullPositionBackups []string 136 for _, tc := range tt { 137 t.Run(tc.name, func(t *testing.T) { 138 if tc.writeBeforeBackup { 139 backup.InsertRowOnPrimary(t, "") 140 } 141 // we wait for 1 second because backups ar ewritten to a directory named after the current timestamp, 142 // in 1 second resolution. We want to aoid two backups that have the same pathname. Realistically this 143 // is only ever a problem in this endtoend test, not in production. 144 // Also, we gie the replica a chance to catch up. 145 time.Sleep(1100 * time.Millisecond) 146 waitForReplica(t) 147 recordRowsPerPosition(t) 148 // configure --incremental-from-pos to either: 149 // - auto 150 // - explicit last backup pos 151 // - back in history to the original full backup 152 var incrementalFromPos mysql.Position 153 if !tc.autoPosition { 154 incrementalFromPos = lastBackupPos 155 if tc.fromFullPosition { 156 incrementalFromPos = fullBackupPos 157 } 158 } 159 manifest, backupName := backup.TestReplicaIncrementalBackup(t, incrementalFromPos, tc.expectError) 160 if tc.expectError != "" { 161 return 162 } 163 defer func() { 164 lastBackupPos = manifest.Position 165 }() 166 if tc.fromFullPosition { 167 fromFullPositionBackups = append(fromFullPositionBackups, backupName) 168 } 169 require.False(t, manifest.FromPosition.IsZero()) 170 require.NotEqual(t, manifest.Position, manifest.FromPosition) 171 require.True(t, manifest.Position.GTIDSet.Contains(manifest.FromPosition.GTIDSet)) 172 173 gtidPurgedPos, err := mysql.ParsePosition(mysql.Mysql56FlavorID, backup.GetReplicaGtidPurged(t)) 174 require.NoError(t, err) 175 fromPositionIncludingPurged := manifest.FromPosition.GTIDSet.Union(gtidPurgedPos.GTIDSet) 176 177 expectFromPosition := lastBackupPos.GTIDSet.Union(gtidPurgedPos.GTIDSet) 178 if !incrementalFromPos.IsZero() { 179 expectFromPosition = incrementalFromPos.GTIDSet.Union(gtidPurgedPos.GTIDSet) 180 } 181 require.Equalf(t, expectFromPosition, fromPositionIncludingPurged, "expected: %v, found: %v", expectFromPosition, fromPositionIncludingPurged) 182 }) 183 } 184 185 testRestores := func(t *testing.T) { 186 for _, r := range rand.Perm(len(backupPositions)) { 187 pos := backupPositions[r] 188 testName := fmt.Sprintf("%s, %d records", pos, rowsPerPosition[pos]) 189 t.Run(testName, func(t *testing.T) { 190 restoreToPos, err := mysql.DecodePosition(pos) 191 require.NoError(t, err) 192 backup.TestReplicaRestoreToPos(t, restoreToPos, "") 193 msgs := backup.ReadRowsFromReplica(t) 194 count, ok := rowsPerPosition[pos] 195 require.True(t, ok) 196 assert.Equalf(t, count, len(msgs), "messages: %v", msgs) 197 }) 198 } 199 } 200 t.Run("PITR", func(t *testing.T) { 201 testRestores(t) 202 }) 203 t.Run("remove full position backups", func(t *testing.T) { 204 // Delete the fromFullPosition backup(s), which leaves us with less restore options. Try again. 205 for _, backupName := range fromFullPositionBackups { 206 backup.RemoveBackup(t, backupName) 207 } 208 }) 209 t.Run("PITR-2", func(t *testing.T) { 210 testRestores(t) 211 }) 212 }