github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/backupccl/restore_mid_schema_change_test.go (about) 1 // Copyright 2020 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package backupccl_test 10 11 import ( 12 "context" 13 "fmt" 14 "io/ioutil" 15 "os" 16 "path/filepath" 17 "strconv" 18 "testing" 19 "time" 20 21 "github.com/cockroachdb/cockroach/pkg/base" 22 "github.com/cockroachdb/cockroach/pkg/jobs" 23 "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" 24 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 25 "github.com/cockroachdb/cockroach/pkg/util/log" 26 "github.com/stretchr/testify/require" 27 ) 28 29 // TestRestoreMidSchemaChanges attempts to RESTORE several BACKUPs that are 30 // already constructed and store in 31 // ccl/backupccl/testdata/restore_mid_schema_change. These backups were taken on 32 // tables that were in the process of performing a schema change. In particular, 33 // the schema changes were temporarily blocked after it completed its backfill 34 // stage. 35 // 36 // This test ensures that these BACKUPS can be: 37 // 1) Restore 38 // 2) Can read the data from the restored table 39 // 3) The schema changes that were in progress on the table complete and are 40 // applied. 41 // 4) Can apply new schema changes. (This is used to ensure that there are no 42 // more mutations hanging on the table descriptor.) 43 // 44 // The test cases are organized based on cluster version of the cluster that 45 // took the BACKUP. Each test-case represents a BACKUP. They were created using 46 // the statements provided in the create.sql file in the appropriate testdata 47 // dir. All backups backed up defaultdb.*, which contain the relevant tables. 48 // Most backups contain a single table whose name matches the backup name. If 49 // the backup is expected to contain several tables, the table names will be 50 // backupName1, backupName2, ... 51 func TestRestoreMidSchemaChange(t *testing.T) { 52 defer leaktest.AfterTest(t)() 53 const ( 54 testdataBase = "testdata/restore_mid_schema_change" 55 exportDirs = testdataBase + "/exports" 56 ) 57 versionDirs, err := ioutil.ReadDir(exportDirs) 58 require.NoError(t, err) 59 for _, clusterVersionDir := range versionDirs { 60 require.True(t, clusterVersionDir.IsDir()) 61 fullClusterVersionDir, err := filepath.Abs(filepath.Join(exportDirs, clusterVersionDir.Name())) 62 require.NoError(t, err) 63 backupDirs, err := ioutil.ReadDir(fullClusterVersionDir) 64 require.NoError(t, err) 65 // In each version folder (e.g. "19.2", "20.1"), there is a backup for each schema change. 66 for _, backupDir := range backupDirs { 67 fullBackupDir, err := filepath.Abs(filepath.Join(fullClusterVersionDir, backupDir.Name())) 68 require.NoError(t, err) 69 t.Run(clusterVersionDir.Name()+"-"+backupDir.Name(), restoreMidSchemaChange(fullBackupDir, backupDir.Name())) 70 } 71 } 72 } 73 74 func restoreMidSchemaChange(backupDir, schemaChangeName string) func(t *testing.T) { 75 verify := func(t *testing.T, scName string, sqlDB *sqlutils.SQLRunner) { 76 var expectedData [][]string 77 tableName := fmt.Sprintf("defaultdb.%s", scName) 78 sqlDB.CheckQueryResultsRetry(t, "SELECT * FROM crdb_internal.jobs WHERE job_type = 'SCHEMA CHANGE' AND status <> 'succeeded'", [][]string{}) 79 numSchemaChangeJobs := 1 80 // This enumerates the tests cases and specifies how each case should be 81 // handled. 82 switch scName { 83 case "midaddcol": 84 expectedData = [][]string{{"1", "1.3"}, {"2", "1.3"}, {"3", "1.3"}} 85 case "midaddconst": 86 expectedData = [][]string{{"1"}, {"2"}, {"3"}} 87 sqlDB.CheckQueryResults(t, "SELECT count(*) FROM [SHOW CONSTRAINTS FROM defaultdb.midaddconst] WHERE constraint_name = 'my_const'", [][]string{{"1"}}) 88 case "midaddindex": 89 expectedData = [][]string{{"1"}, {"2"}, {"3"}} 90 sqlDB.CheckQueryResults(t, "SELECT count(*) FROM [SHOW INDEXES FROM defaultdb.midaddindex] WHERE column_name = 'a'", [][]string{{"1"}}) 91 case "middropcol": 92 expectedData = [][]string{{"1"}, {"1"}, {"1"}, {"2"}, {"2"}, {"2"}, {"3"}, {"3"}, {"3"}} 93 case "midmany": 94 numSchemaChangeJobs = 3 95 expectedData = [][]string{{"1", "1.3"}, {"2", "1.3"}, {"3", "1.3"}} 96 sqlDB.CheckQueryResults(t, "SELECT count(*) FROM [SHOW CONSTRAINTS FROM defaultdb.midmany] WHERE constraint_name = 'my_const'", [][]string{{"1"}}) 97 sqlDB.CheckQueryResults(t, "SELECT count(*) FROM [SHOW INDEXES FROM defaultdb.midmany] WHERE column_name = 'a'", [][]string{{"1"}}) 98 case "midmultitxn": 99 expectedData = [][]string{{"1", "1.3"}, {"2", "1.3"}, {"3", "1.3"}} 100 sqlDB.CheckQueryResults(t, "SELECT count(*) FROM [SHOW CONSTRAINTS FROM defaultdb.midmultitxn] WHERE constraint_name = 'my_const'", [][]string{{"1"}}) 101 sqlDB.CheckQueryResults(t, "SELECT count(*) FROM [SHOW INDEXES FROM defaultdb.midmultitxn] WHERE column_name = 'a'", [][]string{{"1"}}) 102 case "midmultitable": 103 numSchemaChangeJobs = 2 104 expectedData = [][]string{{"1", "1.3"}, {"2", "1.3"}, {"3", "1.3"}} 105 sqlDB.CheckQueryResults(t, fmt.Sprintf("SELECT * FROM %s1", tableName), expectedData) 106 expectedData = [][]string{{"1"}, {"2"}, {"3"}} 107 sqlDB.CheckQueryResults(t, fmt.Sprintf("SELECT * FROM %s2", tableName), expectedData) 108 tableName += "1" 109 case "midprimarykeyswap": 110 // The primary key swap will also create a cleanup job. 111 numSchemaChangeJobs = 2 112 expectedData = [][]string{{"1"}, {"2"}, {"3"}} 113 case "midprimarykeyswapcleanup": 114 // This backup only contains the cleanup job mentioned above. 115 expectedData = [][]string{{"1"}, {"2"}, {"3"}} 116 } 117 if scName != "midmultitable" { 118 sqlDB.CheckQueryResults(t, fmt.Sprintf("SELECT * FROM %s", tableName), expectedData) 119 } 120 sqlDB.CheckQueryResults(t, "SELECT count(*) FROM crdb_internal.jobs WHERE job_type = 'SCHEMA CHANGE'", [][]string{{strconv.Itoa(numSchemaChangeJobs)}}) 121 // Ensure that a schema change can complete on the restored table. 122 schemaChangeQuery := fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT post_restore_const CHECK (a > 0)", tableName) 123 sqlDB.Exec(t, schemaChangeQuery) 124 } 125 126 return func(t *testing.T) { 127 params := base.TestServerArgs{} 128 defer func(oldInterval time.Duration) { 129 jobs.DefaultAdoptInterval = oldInterval 130 }(jobs.DefaultAdoptInterval) 131 jobs.DefaultAdoptInterval = 100 * time.Millisecond 132 const numAccounts = 1000 133 _, _, sqlDB, dir, cleanup := backupRestoreTestSetupWithParams(t, singleNode, numAccounts, 134 initNone, base.TestClusterArgs{ServerArgs: params}) 135 defer cleanup() 136 symlink := filepath.Join(dir, "foo") 137 err := os.Symlink(backupDir, symlink) 138 require.NoError(t, err) 139 sqlDB.Exec(t, "USE defaultdb") 140 restoreQuery := fmt.Sprintf("RESTORE defaultdb.* from $1") 141 log.Infof(context.Background(), "%+v", sqlDB.QueryStr(t, "SHOW BACKUP $1", localFoo)) 142 sqlDB.Exec(t, restoreQuery, localFoo) 143 verify(t, schemaChangeName, sqlDB) 144 } 145 }