vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/health_streamer_test.go (about) 1 /* 2 Copyright 2020 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 tabletserver 18 19 import ( 20 "context" 21 "errors" 22 "sort" 23 "sync" 24 "testing" 25 "time" 26 27 "github.com/stretchr/testify/assert" 28 "google.golang.org/protobuf/proto" 29 30 "vitess.io/vitess/go/sync2" 31 32 "vitess.io/vitess/go/mysql" 33 "vitess.io/vitess/go/mysql/fakesqldb" 34 "vitess.io/vitess/go/sqltypes" 35 querypb "vitess.io/vitess/go/vt/proto/query" 36 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 37 "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" 38 ) 39 40 func TestHealthStreamerClosed(t *testing.T) { 41 db := fakesqldb.New(t) 42 defer db.Close() 43 config := newConfig(db) 44 env := tabletenv.NewEnv(config, "ReplTrackerTest") 45 alias := &topodatapb.TabletAlias{ 46 Cell: "cell", 47 Uid: 1, 48 } 49 blpFunc = testBlpFunc 50 hs := newHealthStreamer(env, alias) 51 err := hs.Stream(context.Background(), func(shr *querypb.StreamHealthResponse) error { 52 return nil 53 }) 54 assert.Contains(t, err.Error(), "tabletserver is shutdown") 55 } 56 57 func newConfig(db *fakesqldb.DB) *tabletenv.TabletConfig { 58 cfg := tabletenv.NewDefaultConfig() 59 cfg.DB = newDBConfigs(db) 60 return cfg 61 } 62 63 func TestHealthStreamerBroadcast(t *testing.T) { 64 db := fakesqldb.New(t) 65 defer db.Close() 66 config := newConfig(db) 67 config.SignalWhenSchemaChange = false 68 69 env := tabletenv.NewEnv(config, "ReplTrackerTest") 70 alias := &topodatapb.TabletAlias{ 71 Cell: "cell", 72 Uid: 1, 73 } 74 blpFunc = testBlpFunc 75 hs := newHealthStreamer(env, alias) 76 hs.InitDBConfig(&querypb.Target{TabletType: topodatapb.TabletType_PRIMARY}, config.DB.DbaWithDB()) 77 hs.Open() 78 defer hs.Close() 79 target := &querypb.Target{} 80 hs.InitDBConfig(target, db.ConnParams()) 81 82 ch, cancel := testStream(hs) 83 defer cancel() 84 85 shr := <-ch 86 want := &querypb.StreamHealthResponse{ 87 Target: &querypb.Target{}, 88 TabletAlias: alias, 89 RealtimeStats: &querypb.RealtimeStats{ 90 HealthError: "tabletserver uninitialized", 91 }, 92 } 93 assert.Truef(t, proto.Equal(want, shr), "want: %v, got: %v", want, shr) 94 95 hs.ChangeState(topodatapb.TabletType_REPLICA, time.Time{}, 0, nil, false) 96 shr = <-ch 97 want = &querypb.StreamHealthResponse{ 98 Target: &querypb.Target{ 99 TabletType: topodatapb.TabletType_REPLICA, 100 }, 101 TabletAlias: alias, 102 RealtimeStats: &querypb.RealtimeStats{ 103 FilteredReplicationLagSeconds: 1, 104 BinlogPlayersCount: 2, 105 }, 106 } 107 assert.Truef(t, proto.Equal(want, shr), "want: %v, got: %v", want, shr) 108 109 // Test primary and timestamp. 110 now := time.Now() 111 hs.ChangeState(topodatapb.TabletType_PRIMARY, now, 0, nil, true) 112 shr = <-ch 113 want = &querypb.StreamHealthResponse{ 114 Target: &querypb.Target{ 115 TabletType: topodatapb.TabletType_PRIMARY, 116 }, 117 TabletAlias: alias, 118 Serving: true, 119 TabletExternallyReparentedTimestamp: now.Unix(), 120 RealtimeStats: &querypb.RealtimeStats{ 121 FilteredReplicationLagSeconds: 1, 122 BinlogPlayersCount: 2, 123 }, 124 } 125 assert.Truef(t, proto.Equal(want, shr), "want: %v, got: %v", want, shr) 126 127 // Test non-serving, and 0 timestamp for non-primary. 128 hs.ChangeState(topodatapb.TabletType_REPLICA, now, 1*time.Second, nil, false) 129 shr = <-ch 130 want = &querypb.StreamHealthResponse{ 131 Target: &querypb.Target{ 132 TabletType: topodatapb.TabletType_REPLICA, 133 }, 134 TabletAlias: alias, 135 RealtimeStats: &querypb.RealtimeStats{ 136 ReplicationLagSeconds: 1, 137 FilteredReplicationLagSeconds: 1, 138 BinlogPlayersCount: 2, 139 }, 140 } 141 assert.Truef(t, proto.Equal(want, shr), "want: %v, got: %v", want, shr) 142 143 // Test Health error. 144 hs.ChangeState(topodatapb.TabletType_REPLICA, now, 0, errors.New("repl err"), false) 145 shr = <-ch 146 want = &querypb.StreamHealthResponse{ 147 Target: &querypb.Target{ 148 TabletType: topodatapb.TabletType_REPLICA, 149 }, 150 TabletAlias: alias, 151 RealtimeStats: &querypb.RealtimeStats{ 152 HealthError: "repl err", 153 FilteredReplicationLagSeconds: 1, 154 BinlogPlayersCount: 2, 155 }, 156 } 157 assert.Truef(t, proto.Equal(want, shr), "want: %v, got: %v", want, shr) 158 } 159 160 func TestReloadSchema(t *testing.T) { 161 db := fakesqldb.New(t) 162 defer db.Close() 163 config := newConfig(db) 164 config.SignalSchemaChangeReloadIntervalSeconds.Set(100 * time.Millisecond) 165 config.SignalWhenSchemaChange = true 166 167 env := tabletenv.NewEnv(config, "ReplTrackerTest") 168 alias := &topodatapb.TabletAlias{ 169 Cell: "cell", 170 Uid: 1, 171 } 172 blpFunc = testBlpFunc 173 hs := newHealthStreamer(env, alias) 174 175 target := &querypb.Target{TabletType: topodatapb.TabletType_PRIMARY} 176 configs := config.DB 177 178 db.AddQueryPattern(mysql.ClearSchemaCopy+".*", &sqltypes.Result{}) 179 db.AddQueryPattern(mysql.InsertIntoSchemaCopy+".*", &sqltypes.Result{}) 180 db.AddQuery("begin", &sqltypes.Result{}) 181 db.AddQuery("commit", &sqltypes.Result{}) 182 db.AddQuery("rollback", &sqltypes.Result{}) 183 db.AddQuery(mysql.DetectSchemaChange, sqltypes.MakeTestResult( 184 sqltypes.MakeTestFields( 185 "table_name", 186 "varchar", 187 ), 188 "product", 189 "users", 190 )) 191 db.AddQuery(mysql.SelectAllViews, &sqltypes.Result{}) 192 193 hs.InitDBConfig(target, configs.DbaWithDB()) 194 hs.Open() 195 defer hs.Close() 196 var wg sync.WaitGroup 197 wg.Add(1) 198 go func() { 199 hs.Stream(ctx, func(response *querypb.StreamHealthResponse) error { 200 if response.RealtimeStats.TableSchemaChanged != nil { 201 assert.Equal(t, []string{"product", "users"}, response.RealtimeStats.TableSchemaChanged) 202 wg.Done() 203 } 204 return nil 205 }) 206 }() 207 208 c := make(chan struct{}) 209 go func() { 210 defer close(c) 211 wg.Wait() 212 }() 213 select { 214 case <-c: 215 case <-time.After(1 * time.Second): 216 t.Errorf("timed out") 217 } 218 } 219 220 func TestDoesNotReloadSchema(t *testing.T) { 221 db := fakesqldb.New(t) 222 defer db.Close() 223 config := newConfig(db) 224 config.SignalSchemaChangeReloadIntervalSeconds.Set(100 * time.Millisecond) 225 config.SignalWhenSchemaChange = false 226 227 env := tabletenv.NewEnv(config, "ReplTrackerTest") 228 alias := &topodatapb.TabletAlias{ 229 Cell: "cell", 230 Uid: 1, 231 } 232 blpFunc = testBlpFunc 233 hs := newHealthStreamer(env, alias) 234 235 target := &querypb.Target{TabletType: topodatapb.TabletType_PRIMARY} 236 configs := config.DB 237 238 hs.InitDBConfig(target, configs.DbaWithDB()) 239 hs.Open() 240 defer hs.Close() 241 var wg sync.WaitGroup 242 wg.Add(1) 243 go func() { 244 hs.Stream(ctx, func(response *querypb.StreamHealthResponse) error { 245 if response.RealtimeStats.TableSchemaChanged != nil { 246 wg.Done() 247 } 248 return nil 249 }) 250 }() 251 252 c := make(chan struct{}) 253 go func() { 254 defer close(c) 255 wg.Wait() 256 }() 257 258 timeout := false 259 260 // here we will wait for a second, to make sure that we are not signaling a changed schema. 261 select { 262 case <-c: 263 case <-time.After(1 * time.Second): 264 timeout = true 265 } 266 267 assert.True(t, timeout, "should have timed out") 268 } 269 270 func TestInitialReloadSchema(t *testing.T) { 271 db := fakesqldb.New(t) 272 defer db.Close() 273 config := newConfig(db) 274 // Setting the signal schema change reload interval to one minute 275 // that way we can test the initial reload trigger. 276 config.SignalSchemaChangeReloadIntervalSeconds.Set(1 * time.Minute) 277 config.SignalWhenSchemaChange = true 278 279 env := tabletenv.NewEnv(config, "ReplTrackerTest") 280 alias := &topodatapb.TabletAlias{ 281 Cell: "cell", 282 Uid: 1, 283 } 284 blpFunc = testBlpFunc 285 hs := newHealthStreamer(env, alias) 286 287 target := &querypb.Target{TabletType: topodatapb.TabletType_PRIMARY} 288 configs := config.DB 289 290 db.AddQueryPattern(mysql.ClearSchemaCopy+".*", &sqltypes.Result{}) 291 db.AddQueryPattern(mysql.InsertIntoSchemaCopy+".*", &sqltypes.Result{}) 292 db.AddQuery("begin", &sqltypes.Result{}) 293 db.AddQuery("commit", &sqltypes.Result{}) 294 db.AddQuery("rollback", &sqltypes.Result{}) 295 db.AddQuery(mysql.DetectSchemaChange, sqltypes.MakeTestResult( 296 sqltypes.MakeTestFields( 297 "table_name", 298 "varchar", 299 ), 300 "product", 301 "users", 302 )) 303 db.AddQuery(mysql.SelectAllViews, &sqltypes.Result{}) 304 305 hs.InitDBConfig(target, configs.DbaWithDB()) 306 hs.Open() 307 defer hs.Close() 308 var wg sync.WaitGroup 309 wg.Add(1) 310 go func() { 311 hs.Stream(ctx, func(response *querypb.StreamHealthResponse) error { 312 if response.RealtimeStats.TableSchemaChanged != nil { 313 assert.Equal(t, []string{"product", "users"}, response.RealtimeStats.TableSchemaChanged) 314 wg.Done() 315 } 316 return nil 317 }) 318 }() 319 320 c := make(chan struct{}) 321 go func() { 322 defer close(c) 323 wg.Wait() 324 }() 325 select { 326 case <-c: 327 case <-time.After(1 * time.Second): 328 // should not timeout despite SignalSchemaChangeReloadIntervalSeconds being set to 1 minute 329 t.Errorf("timed out") 330 } 331 } 332 333 // TestReloadView tests that the health streamer tracks view changes correctly 334 func TestReloadView(t *testing.T) { 335 db := fakesqldb.New(t) 336 defer db.Close() 337 config := newConfig(db) 338 config.SignalSchemaChangeReloadIntervalSeconds.Set(100 * time.Millisecond) 339 config.EnableViews = true 340 341 env := tabletenv.NewEnv(config, "TestReloadView") 342 alias := &topodatapb.TabletAlias{Cell: "cell", Uid: 1} 343 hs := newHealthStreamer(env, alias) 344 345 target := &querypb.Target{TabletType: topodatapb.TabletType_PRIMARY} 346 configs := config.DB 347 348 db.AddQuery(mysql.DetectSchemaChangeOnlyBaseTable, &sqltypes.Result{}) 349 db.AddQuery(mysql.SelectAllViews, &sqltypes.Result{}) 350 351 hs.InitDBConfig(target, configs.DbaWithDB()) 352 hs.Open() 353 defer hs.Close() 354 355 tcases := []struct { 356 res *sqltypes.Result 357 exp []string 358 }{{ 359 // view_a and view_b added. 360 res: sqltypes.MakeTestResult(sqltypes.MakeTestFields("table_name|updated_at", "varchar|timestamp"), 361 "view_a|2023-01-12 14:23:33", "view_b|2023-01-12 15:23:33"), 362 exp: []string{"view_a", "view_b"}, 363 }, { 364 // view_b modified 365 res: sqltypes.MakeTestResult(sqltypes.MakeTestFields("table_name|updated_at", "varchar|timestamp"), 366 "view_a|2023-01-12 14:23:33", "view_b|2023-01-12 18:23:33"), 367 exp: []string{"view_b"}, 368 }, { 369 // view_a modified, view_b deleted and view_c added. 370 res: sqltypes.MakeTestResult(sqltypes.MakeTestFields("table_name|updated_at", "varchar|timestamp"), 371 "view_a|2023-01-12 16:23:33", "view_c|2023-01-12 18:23:33"), 372 exp: []string{"view_a", "view_b", "view_c"}, 373 }} 374 375 // setting first test case result. 376 db.AddQuery(mysql.SelectAllViews, tcases[0].res) 377 378 var tcCount sync2.AtomicInt32 379 ch := make(chan struct{}) 380 381 go func() { 382 hs.Stream(ctx, func(response *querypb.StreamHealthResponse) error { 383 if response.RealtimeStats.ViewSchemaChanged != nil { 384 sort.Strings(response.RealtimeStats.ViewSchemaChanged) 385 assert.Equal(t, tcases[tcCount.Get()].exp, response.RealtimeStats.ViewSchemaChanged) 386 tcCount.Add(1) 387 ch <- struct{}{} 388 } 389 return nil 390 }) 391 }() 392 393 for { 394 select { 395 case <-ch: 396 if tcCount.Get() == int32(len(tcases)) { 397 return 398 } 399 db.AddQuery(mysql.SelectAllViews, tcases[tcCount.Get()].res) 400 case <-time.After(1000 * time.Second): 401 t.Fatalf("timed out") 402 } 403 } 404 405 } 406 407 func testStream(hs *healthStreamer) (<-chan *querypb.StreamHealthResponse, context.CancelFunc) { 408 ctx, cancel := context.WithCancel(context.Background()) 409 ch := make(chan *querypb.StreamHealthResponse) 410 go func() { 411 _ = hs.Stream(ctx, func(shr *querypb.StreamHealthResponse) error { 412 ch <- shr 413 return nil 414 }) 415 }() 416 return ch, cancel 417 } 418 419 func testBlpFunc() (int64, int32) { 420 return 1, 2 421 }