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  }