vitess.io/vitess@v0.16.2/go/vt/vtgate/schema/tracker_test.go (about) 1 /* 2 Copyright 2021 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 schema 18 19 import ( 20 "fmt" 21 "sync" 22 "testing" 23 "time" 24 25 "vitess.io/vitess/go/mysql" 26 27 "github.com/stretchr/testify/assert" 28 29 "github.com/stretchr/testify/require" 30 31 "vitess.io/vitess/go/sqltypes" 32 "vitess.io/vitess/go/test/utils" 33 "vitess.io/vitess/go/vt/discovery" 34 querypb "vitess.io/vitess/go/vt/proto/query" 35 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 36 "vitess.io/vitess/go/vt/sqlparser" 37 "vitess.io/vitess/go/vt/vtgate/vindexes" 38 "vitess.io/vitess/go/vt/vttablet/sandboxconn" 39 ) 40 41 func TestTracking(t *testing.T) { 42 target := &querypb.Target{ 43 Keyspace: "ks", 44 Shard: "-80", 45 TabletType: topodatapb.TabletType_PRIMARY, 46 Cell: "aa", 47 } 48 tablet := &topodatapb.Tablet{ 49 Keyspace: target.Keyspace, 50 Shard: target.Shard, 51 Type: target.TabletType, 52 } 53 fields := sqltypes.MakeTestFields( 54 "table_name|col_name|col_type|collation_name", 55 "varchar|varchar|varchar|varchar", 56 ) 57 58 type delta struct { 59 result *sqltypes.Result 60 updTbl []string 61 } 62 var ( 63 d0 = delta{ 64 result: sqltypes.MakeTestResult( 65 fields, 66 "prior|id|int|", 67 ), 68 updTbl: []string{"prior"}, 69 } 70 71 d1 = delta{ 72 result: sqltypes.MakeTestResult( 73 fields, 74 "t1|id|int|", 75 "t1|name|varchar|utf8_bin", 76 "t2|id|varchar|utf8_bin", 77 ), 78 updTbl: []string{"t1", "t2"}, 79 } 80 81 d2 = delta{ 82 result: sqltypes.MakeTestResult( 83 fields, 84 "t2|id|varchar|utf8_bin", 85 "t2|name|varchar|utf8_bin", 86 "t3|id|datetime|", 87 ), 88 updTbl: []string{"prior", "t1", "t2", "t3"}, 89 } 90 91 d3 = delta{ 92 result: sqltypes.MakeTestResult( 93 fields, 94 "t4|name|varchar|utf8_bin", 95 ), 96 updTbl: []string{"t4"}, 97 } 98 ) 99 100 testcases := []struct { 101 tName string 102 deltas []delta 103 exp map[string][]vindexes.Column 104 }{{ 105 tName: "new tables", 106 deltas: []delta{d0, d1}, 107 exp: map[string][]vindexes.Column{ 108 "t1": { 109 {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_INT32}, 110 {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}}, 111 "t2": { 112 {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}}, 113 "prior": { 114 {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_INT32}}, 115 }, 116 }, { 117 tName: "delete t1 and prior, updated t2 and new t3", 118 deltas: []delta{d0, d1, d2}, 119 exp: map[string][]vindexes.Column{ 120 "t2": { 121 {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}, 122 {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}}, 123 "t3": { 124 {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_DATETIME}}, 125 }, 126 }, { 127 tName: "new t4", 128 deltas: []delta{d0, d1, d2, d3}, 129 exp: map[string][]vindexes.Column{ 130 "t2": { 131 {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}, 132 {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}}, 133 "t3": { 134 {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_DATETIME}}, 135 "t4": { 136 {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}}, 137 }, 138 }, 139 } 140 for i, tcase := range testcases { 141 t.Run(fmt.Sprintf("%d - %s", i, tcase.tName), func(t *testing.T) { 142 sbc := sandboxconn.NewSandboxConn(tablet) 143 ch := make(chan *discovery.TabletHealth) 144 tracker := NewTracker(ch, "", false) 145 tracker.consumeDelay = 1 * time.Millisecond 146 tracker.Start() 147 defer tracker.Stop() 148 149 results := []*sqltypes.Result{{}} 150 for _, d := range tcase.deltas { 151 for _, deltaRow := range d.result.Rows { 152 same := false 153 for _, row := range results[0].Rows { 154 if row[0].String() == deltaRow[0].String() && row[1].String() == deltaRow[1].String() { 155 same = true 156 break 157 } 158 } 159 if same == false { 160 results[0].Rows = append(results[0].Rows, deltaRow) 161 } 162 } 163 } 164 165 sbc.SetResults(results) 166 sbc.Queries = nil 167 168 wg := sync.WaitGroup{} 169 wg.Add(1) 170 tracker.RegisterSignalReceiver(func() { 171 wg.Done() 172 }) 173 174 for _, d := range tcase.deltas { 175 ch <- &discovery.TabletHealth{ 176 Conn: sbc, 177 Tablet: tablet, 178 Target: target, 179 Serving: true, 180 Stats: &querypb.RealtimeStats{TableSchemaChanged: d.updTbl}, 181 } 182 } 183 184 require.False(t, waitTimeout(&wg, time.Second), "schema was updated but received no signal") 185 186 require.Equal(t, 1, len(sbc.StringQueries())) 187 188 _, keyspacePresent := tracker.tracked[target.Keyspace] 189 require.Equal(t, true, keyspacePresent) 190 191 for k, v := range tcase.exp { 192 utils.MustMatch(t, v, tracker.GetColumns("ks", k), "mismatch for table: ", k) 193 } 194 }) 195 } 196 } 197 198 func TestTrackingUnHealthyTablet(t *testing.T) { 199 target := &querypb.Target{ 200 Keyspace: "ks", 201 Shard: "-80", 202 TabletType: topodatapb.TabletType_PRIMARY, 203 Cell: "aa", 204 } 205 tablet := &topodatapb.Tablet{ 206 Keyspace: target.Keyspace, 207 Shard: target.Shard, 208 Type: target.TabletType, 209 } 210 211 sbc := sandboxconn.NewSandboxConn(tablet) 212 ch := make(chan *discovery.TabletHealth) 213 tracker := NewTracker(ch, "", false) 214 tracker.consumeDelay = 1 * time.Millisecond 215 tracker.Start() 216 defer tracker.Stop() 217 218 // the test are written in a way that it expects 3 signals to be sent from the tracker to the subscriber. 219 wg := sync.WaitGroup{} 220 wg.Add(3) 221 tracker.RegisterSignalReceiver(func() { 222 wg.Done() 223 }) 224 225 tcases := []struct { 226 name string 227 serving bool 228 expectedQuery string 229 updatedTbls []string 230 }{ 231 { 232 name: "initial load", 233 serving: true, 234 }, 235 { 236 name: "initial load", 237 serving: true, 238 updatedTbls: []string{"a"}, 239 }, 240 { 241 name: "non serving tablet", 242 serving: false, 243 }, 244 { 245 name: "now serving tablet", 246 serving: true, 247 }, 248 } 249 250 sbc.SetResults([]*sqltypes.Result{{}, {}, {}}) 251 for _, tcase := range tcases { 252 ch <- &discovery.TabletHealth{ 253 Conn: sbc, 254 Tablet: tablet, 255 Target: target, 256 Serving: tcase.serving, 257 Stats: &querypb.RealtimeStats{TableSchemaChanged: tcase.updatedTbls}, 258 } 259 time.Sleep(5 * time.Millisecond) 260 } 261 262 require.False(t, waitTimeout(&wg, 5*time.Second), "schema was updated but received no signal") 263 require.Equal(t, []string{mysql.FetchTables, mysql.FetchUpdatedTables, mysql.FetchTables}, sbc.StringQueries()) 264 } 265 266 func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { 267 c := make(chan struct{}) 268 go func() { 269 defer close(c) 270 wg.Wait() 271 }() 272 select { 273 case <-c: 274 return false // completed normally 275 case <-time.After(timeout): 276 return true // timed out 277 } 278 } 279 280 func TestTrackerGetKeyspaceUpdateController(t *testing.T) { 281 ks3 := &updateController{} 282 tracker := Tracker{ 283 tracked: map[keyspaceStr]*updateController{ 284 "ks3": ks3, 285 }, 286 } 287 288 th1 := &discovery.TabletHealth{ 289 Target: &querypb.Target{Keyspace: "ks1"}, 290 } 291 ks1 := tracker.getKeyspaceUpdateController(th1) 292 293 th2 := &discovery.TabletHealth{ 294 Target: &querypb.Target{Keyspace: "ks2"}, 295 } 296 ks2 := tracker.getKeyspaceUpdateController(th2) 297 298 th3 := &discovery.TabletHealth{ 299 Target: &querypb.Target{Keyspace: "ks3"}, 300 } 301 302 assert.NotEqual(t, ks1, ks2, "ks1 and ks2 should not be equal, belongs to different keyspace") 303 assert.Equal(t, ks1, tracker.getKeyspaceUpdateController(th1), "received different updateController") 304 assert.Equal(t, ks2, tracker.getKeyspaceUpdateController(th2), "received different updateController") 305 assert.Equal(t, ks3, tracker.getKeyspaceUpdateController(th3), "received different updateController") 306 307 assert.NotNil(t, ks1.reloadKeyspace, "ks1 needs to be initialized") 308 assert.NotNil(t, ks2.reloadKeyspace, "ks2 needs to be initialized") 309 assert.Nil(t, ks3.reloadKeyspace, "ks3 already initialized") 310 } 311 312 // TestViewsTracking tests that the tracker is able to track views. 313 func TestViewsTracking(t *testing.T) { 314 target := &querypb.Target{Cell: "aa", Keyspace: "ks", Shard: "-80", TabletType: topodatapb.TabletType_PRIMARY} 315 tablet := &topodatapb.Tablet{Keyspace: target.Keyspace, Shard: target.Shard, Type: target.TabletType} 316 317 schemaDefResult := []map[string]string{{ 318 "prior": "create view prior as select 1 from tbl", 319 "t1": "create view t1 as select 1 from tbl1", 320 "t2": "create view t2 as select 1 from tbl2", 321 }, { 322 "t2": "create view t2 as select 1,2 from tbl2", 323 "t3": "create view t3 as select 1 from tbl3", 324 }, { 325 "t4": "create view t4 as select 1 from tbl4", 326 }} 327 328 testcases := []struct { 329 testName string 330 updView []string 331 exp map[string]string 332 }{{ 333 testName: "new views", 334 updView: []string{"prior", "t1", "t2"}, 335 exp: map[string]string{ 336 "t1": "select 1 from tbl1", 337 "t2": "select 1 from tbl2", 338 "prior": "select 1 from tbl"}, 339 }, { 340 testName: "delete prior, updated t2 and new t3", 341 updView: []string{"prior", "t2", "t3"}, 342 exp: map[string]string{ 343 "t1": "select 1 from tbl1", 344 "t2": "select 1, 2 from tbl2", 345 "t3": "select 1 from tbl3"}, 346 }, { 347 testName: "new t4", 348 updView: []string{"t4"}, 349 exp: map[string]string{ 350 "t1": "select 1 from tbl1", 351 "t2": "select 1, 2 from tbl2", 352 "t3": "select 1 from tbl3", 353 "t4": "select 1 from tbl4"}, 354 }} 355 356 ch := make(chan *discovery.TabletHealth) 357 tracker := NewTracker(ch, "", true) 358 tracker.tables = nil // making tables map nil - so load keyspace does not try to load the tables information. 359 tracker.consumeDelay = 1 * time.Millisecond 360 tracker.Start() 361 defer tracker.Stop() 362 363 wg := sync.WaitGroup{} 364 tracker.RegisterSignalReceiver(func() { 365 wg.Done() 366 }) 367 368 sbc := sandboxconn.NewSandboxConn(tablet) 369 sbc.SetSchemaResult(schemaDefResult) 370 371 for count, tcase := range testcases { 372 t.Run(tcase.testName, func(t *testing.T) { 373 wg.Add(1) 374 ch <- &discovery.TabletHealth{ 375 Conn: sbc, 376 Tablet: tablet, 377 Target: target, 378 Serving: true, 379 Stats: &querypb.RealtimeStats{ViewSchemaChanged: tcase.updView}, 380 } 381 382 require.False(t, waitTimeout(&wg, time.Second), "schema was updated but received no signal") 383 require.EqualValues(t, count+1, sbc.GetSchemaCount.Get()) 384 385 _, keyspacePresent := tracker.tracked[target.Keyspace] 386 require.Equal(t, true, keyspacePresent) 387 388 for k, v := range tcase.exp { 389 utils.MustMatch(t, v, sqlparser.String(tracker.GetViews("ks", k)), "mismatch for table: ", k) 390 } 391 }) 392 } 393 }