vitess.io/vitess@v0.16.2/go/vt/wrangler/materializer_env_test.go (about) 1 /* 2 Copyright 2019 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 wrangler 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "regexp" 24 "strconv" 25 "strings" 26 "sync" 27 "testing" 28 29 "vitess.io/vitess/go/sqltypes" 30 "vitess.io/vitess/go/vt/logutil" 31 "vitess.io/vitess/go/vt/mysqlctl/tmutils" 32 "vitess.io/vitess/go/vt/sqlparser" 33 "vitess.io/vitess/go/vt/topo" 34 "vitess.io/vitess/go/vt/topo/memorytopo" 35 "vitess.io/vitess/go/vt/vttablet/tmclient" 36 37 _flag "vitess.io/vitess/go/internal/flag" 38 querypb "vitess.io/vitess/go/vt/proto/query" 39 tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" 40 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 41 vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata" 42 ) 43 44 type testMaterializerEnv struct { 45 wr *Wrangler 46 ms *vtctldatapb.MaterializeSettings 47 sources []string 48 targets []string 49 tablets map[int]*topodatapb.Tablet 50 topoServ *topo.Server 51 cell string 52 tmc *testMaterializerTMClient 53 } 54 55 //---------------------------------------------- 56 // testMaterializerEnv 57 58 func TestMain(m *testing.M) { 59 _flag.ParseFlagsForTest() 60 os.Exit(m.Run()) 61 } 62 63 func newTestMaterializerEnv(t *testing.T, ms *vtctldatapb.MaterializeSettings, sources, targets []string) *testMaterializerEnv { 64 t.Helper() 65 env := &testMaterializerEnv{ 66 ms: ms, 67 sources: sources, 68 targets: targets, 69 tablets: make(map[int]*topodatapb.Tablet), 70 topoServ: memorytopo.NewServer("cell"), 71 cell: "cell", 72 tmc: newTestMaterializerTMClient(), 73 } 74 env.wr = New(logutil.NewConsoleLogger(), env.topoServ, env.tmc) 75 tabletID := 100 76 for _, shard := range sources { 77 _ = env.addTablet(tabletID, env.ms.SourceKeyspace, shard, topodatapb.TabletType_PRIMARY) 78 tabletID += 10 79 } 80 if ms.SourceKeyspace != ms.TargetKeyspace { 81 tabletID = 200 82 for _, shard := range targets { 83 _ = env.addTablet(tabletID, env.ms.TargetKeyspace, shard, topodatapb.TabletType_PRIMARY) 84 tabletID += 10 85 } 86 } 87 88 for _, ts := range ms.TableSettings { 89 tableName := ts.TargetTable 90 table, err := sqlparser.TableFromStatement(ts.SourceExpression) 91 if err == nil { 92 tableName = table.Name.String() 93 } 94 env.tmc.schema[ms.SourceKeyspace+"."+tableName] = &tabletmanagerdatapb.SchemaDefinition{ 95 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 96 Name: tableName, 97 Schema: fmt.Sprintf("%s_schema", tableName), 98 }}, 99 } 100 env.tmc.schema[ms.TargetKeyspace+"."+ts.TargetTable] = &tabletmanagerdatapb.SchemaDefinition{ 101 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 102 Name: ts.TargetTable, 103 Schema: fmt.Sprintf("%s_schema", ts.TargetTable), 104 }}, 105 } 106 } 107 if ms.Workflow != "" { 108 env.expectValidation() 109 } 110 return env 111 } 112 113 func (env *testMaterializerEnv) expectValidation() { 114 for _, tablet := range env.tablets { 115 tabletID := int(tablet.Alias.Uid) 116 if tabletID < 200 { 117 continue 118 } 119 // wr.validateNewWorkflow 120 env.tmc.expectVRQuery(tabletID, fmt.Sprintf("select 1 from _vt.vreplication where db_name='vt_%s' and workflow='%s'", env.ms.TargetKeyspace, env.ms.Workflow), &sqltypes.Result{}) 121 } 122 } 123 124 func (env *testMaterializerEnv) close() { 125 for _, t := range env.tablets { 126 env.deleteTablet(t) 127 } 128 } 129 130 func (env *testMaterializerEnv) addTablet(id int, keyspace, shard string, tabletType topodatapb.TabletType) *topodatapb.Tablet { 131 tablet := &topodatapb.Tablet{ 132 Alias: &topodatapb.TabletAlias{ 133 Cell: env.cell, 134 Uid: uint32(id), 135 }, 136 Keyspace: keyspace, 137 Shard: shard, 138 KeyRange: &topodatapb.KeyRange{}, 139 Type: tabletType, 140 PortMap: map[string]int32{ 141 "test": int32(id), 142 }, 143 } 144 env.tablets[id] = tablet 145 if err := env.wr.TopoServer().InitTablet(context.Background(), tablet, false /* allowPrimaryOverride */, true /* createShardAndKeyspace */, false /* allowUpdate */); err != nil { 146 panic(err) 147 } 148 if tabletType == topodatapb.TabletType_PRIMARY { 149 _, err := env.wr.ts.UpdateShardFields(context.Background(), keyspace, shard, func(si *topo.ShardInfo) error { 150 si.PrimaryAlias = tablet.Alias 151 return nil 152 }) 153 if err != nil { 154 panic(err) 155 } 156 } 157 return tablet 158 } 159 160 func (env *testMaterializerEnv) deleteTablet(tablet *topodatapb.Tablet) { 161 env.topoServ.DeleteTablet(context.Background(), tablet.Alias) 162 delete(env.tablets, int(tablet.Alias.Uid)) 163 } 164 165 //---------------------------------------------- 166 // testMaterializerTMClient 167 168 type testMaterializerTMClient struct { 169 tmclient.TabletManagerClient 170 schema map[string]*tabletmanagerdatapb.SchemaDefinition 171 172 mu sync.Mutex 173 vrQueries map[int][]*queryResult 174 getSchemaCounts map[string]int 175 muSchemaCount sync.Mutex 176 } 177 178 func newTestMaterializerTMClient() *testMaterializerTMClient { 179 return &testMaterializerTMClient{ 180 schema: make(map[string]*tabletmanagerdatapb.SchemaDefinition), 181 vrQueries: make(map[int][]*queryResult), 182 getSchemaCounts: make(map[string]int), 183 } 184 } 185 186 func (tmc *testMaterializerTMClient) schemaRequested(uid uint32) { 187 tmc.muSchemaCount.Lock() 188 defer tmc.muSchemaCount.Unlock() 189 key := strconv.Itoa(int(uid)) 190 n, ok := tmc.getSchemaCounts[key] 191 if !ok { 192 tmc.getSchemaCounts[key] = 1 193 } else { 194 tmc.getSchemaCounts[key] = n + 1 195 } 196 } 197 198 func (tmc *testMaterializerTMClient) getSchemaRequestCount(uid uint32) int { 199 tmc.muSchemaCount.Lock() 200 defer tmc.muSchemaCount.Unlock() 201 key := strconv.Itoa(int(uid)) 202 return tmc.getSchemaCounts[key] 203 } 204 205 func (tmc *testMaterializerTMClient) GetSchema(ctx context.Context, tablet *topodatapb.Tablet, request *tabletmanagerdatapb.GetSchemaRequest) (*tabletmanagerdatapb.SchemaDefinition, error) { 206 tmc.schemaRequested(tablet.Alias.Uid) 207 schemaDefn := &tabletmanagerdatapb.SchemaDefinition{} 208 for _, table := range request.Tables { 209 // TODO: Add generalized regexps if needed for test purposes. 210 if table == "/.*/" { 211 // Special case of all tables in keyspace. 212 for key, tableDefn := range tmc.schema { 213 if strings.HasPrefix(key, tablet.Keyspace+".") { 214 schemaDefn.TableDefinitions = append(schemaDefn.TableDefinitions, tableDefn.TableDefinitions...) 215 } 216 } 217 break 218 } 219 220 key := tablet.Keyspace + "." + table 221 tableDefn := tmc.schema[key] 222 if tableDefn == nil { 223 continue 224 } 225 schemaDefn.TableDefinitions = append(schemaDefn.TableDefinitions, tableDefn.TableDefinitions...) 226 } 227 return schemaDefn, nil 228 } 229 230 func (tmc *testMaterializerTMClient) expectVRQuery(tabletID int, query string, result *sqltypes.Result) { 231 tmc.mu.Lock() 232 defer tmc.mu.Unlock() 233 234 tmc.vrQueries[tabletID] = append(tmc.vrQueries[tabletID], &queryResult{ 235 query: query, 236 result: sqltypes.ResultToProto3(result), 237 }) 238 } 239 240 func (tmc *testMaterializerTMClient) VReplicationExec(ctx context.Context, tablet *topodatapb.Tablet, query string) (*querypb.QueryResult, error) { 241 tmc.mu.Lock() 242 defer tmc.mu.Unlock() 243 244 qrs := tmc.vrQueries[int(tablet.Alias.Uid)] 245 if len(qrs) == 0 { 246 return nil, fmt.Errorf("tablet %v does not expect any more queries: %s", tablet, query) 247 } 248 matched := false 249 if qrs[0].query[0] == '/' { 250 matched = regexp.MustCompile(qrs[0].query[1:]).MatchString(query) 251 } else { 252 matched = query == qrs[0].query 253 } 254 if !matched { 255 return nil, fmt.Errorf("tablet %v:\nunexpected query\n%s\nwant:\n%s", tablet, query, qrs[0].query) 256 } 257 tmc.vrQueries[int(tablet.Alias.Uid)] = qrs[1:] 258 return qrs[0].result, nil 259 } 260 261 func (tmc *testMaterializerTMClient) ExecuteFetchAsDba(ctx context.Context, tablet *topodatapb.Tablet, usePool bool, req *tabletmanagerdatapb.ExecuteFetchAsDbaRequest) (*querypb.QueryResult, error) { 262 // Reuse VReplicationExec 263 return tmc.VReplicationExec(ctx, tablet, string(req.Query)) 264 } 265 266 func (tmc *testMaterializerTMClient) verifyQueries(t *testing.T) { 267 t.Helper() 268 269 tmc.mu.Lock() 270 defer tmc.mu.Unlock() 271 272 for tabletID, qrs := range tmc.vrQueries { 273 if len(qrs) != 0 { 274 var list []string 275 for _, qr := range qrs { 276 list = append(list, qr.query) 277 } 278 t.Errorf("tablet %v: found queries that were expected but never got executed by the test: %v", tabletID, list) 279 } 280 } 281 } 282 283 // Note: ONLY breaks up change.SQL into individual statements and executes it. Does NOT fully implement ApplySchema. 284 func (tmc *testMaterializerTMClient) ApplySchema(ctx context.Context, tablet *topodatapb.Tablet, change *tmutils.SchemaChange) (*tabletmanagerdatapb.SchemaChangeResult, error) { 285 stmts := strings.Split(change.SQL, ";") 286 287 for _, stmt := range stmts { 288 _, err := tmc.ExecuteFetchAsDba(ctx, tablet, false, &tabletmanagerdatapb.ExecuteFetchAsDbaRequest{ 289 Query: []byte(stmt), 290 MaxRows: 0, 291 ReloadSchema: true, 292 }) 293 if err != nil { 294 return nil, err 295 } 296 } 297 298 return nil, nil 299 }