vitess.io/vitess@v0.16.2/go/vt/vtgate/schema/update_controller_flaky_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 "testing" 22 "time" 23 24 "github.com/stretchr/testify/assert" 25 26 "vitess.io/vitess/go/vt/discovery" 27 querypb "vitess.io/vitess/go/vt/proto/query" 28 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 29 ) 30 31 func TestMultipleUpdatesFromDifferentShards(t *testing.T) { 32 type input struct { 33 shard string 34 tablesUpdates []string 35 } 36 type testCase struct { 37 updateTables []string 38 signalExpected, initExpected int 39 inputs []input 40 delay time.Duration 41 init, initFail, updateFail bool 42 } 43 tests := []testCase{{ 44 inputs: []input{{ 45 shard: "-80", 46 tablesUpdates: []string{"a"}, 47 }, { 48 shard: "80-", 49 tablesUpdates: []string{"a"}, 50 }}, 51 updateTables: []string{"a"}, 52 signalExpected: 1, 53 }, { 54 inputs: []input{{ 55 shard: "0", 56 tablesUpdates: []string{"a"}, 57 }, { 58 shard: "0", 59 tablesUpdates: []string{"b"}, 60 }}, 61 updateTables: []string{"a", "b"}, 62 signalExpected: 1, 63 }, { 64 inputs: []input{{ 65 shard: "0", 66 tablesUpdates: []string{"a"}, 67 }, { 68 shard: "0", 69 tablesUpdates: []string{"b"}, 70 }}, 71 updateTables: []string{"b"}, 72 signalExpected: 2, 73 delay: 10 * time.Millisecond, 74 }, { 75 inputs: []input{{ 76 shard: "0", 77 }, { 78 shard: "0", 79 }}, 80 }, { 81 inputs: []input{{ 82 shard: "-80", 83 tablesUpdates: []string{"a"}, 84 }, { 85 shard: "80-", 86 tablesUpdates: []string{"a"}, 87 }}, 88 signalExpected: 1, 89 initExpected: 1, 90 init: true, 91 }, { 92 inputs: []input{{ 93 shard: "-80", 94 tablesUpdates: []string{"a"}, 95 }, { 96 shard: "80-", 97 tablesUpdates: []string{"a"}, 98 }}, 99 signalExpected: 0, 100 initExpected: 1, 101 init: true, 102 initFail: true, 103 }, { 104 inputs: []input{{ 105 shard: "-80", 106 tablesUpdates: []string{"a"}, 107 }, { 108 shard: "80-", 109 tablesUpdates: []string{"b"}, 110 }}, 111 updateTables: []string{"a", "b"}, 112 signalExpected: 0, 113 updateFail: true, 114 }, 115 } 116 for i, test := range tests { 117 t.Run(fmt.Sprintf("%d", i+1), func(t *testing.T) { 118 var signalNb, initNb int 119 var updatedTables []string 120 update := func(th *discovery.TabletHealth) bool { 121 updatedTables = th.Stats.TableSchemaChanged 122 return !test.updateFail 123 } 124 signal := func() { 125 signalNb++ 126 } 127 updateCont := updateController{ 128 update: update, 129 signal: signal, 130 consumeDelay: 5 * time.Millisecond, 131 reloadKeyspace: func(th *discovery.TabletHealth) error { 132 initNb++ 133 var err error 134 if test.initFail { 135 err = fmt.Errorf("error") 136 } 137 return err 138 }, 139 loaded: !test.init, 140 } 141 142 for _, in := range test.inputs { 143 target := &querypb.Target{ 144 Keyspace: "ks", 145 Shard: in.shard, 146 TabletType: topodatapb.TabletType_PRIMARY, 147 } 148 tablet := &topodatapb.Tablet{ 149 Keyspace: target.Keyspace, 150 Shard: target.Shard, 151 Type: target.TabletType, 152 } 153 d := &discovery.TabletHealth{ 154 Tablet: tablet, 155 Target: target, 156 Serving: true, 157 Stats: &querypb.RealtimeStats{TableSchemaChanged: in.tablesUpdates}, 158 } 159 if test.delay > 0 { 160 time.Sleep(test.delay) 161 } 162 updateCont.add(d) 163 } 164 165 for { 166 updateCont.mu.Lock() 167 done := updateCont.queue == nil 168 updateCont.mu.Unlock() 169 if done { 170 break 171 } 172 } 173 174 assert.Equal(t, test.signalExpected, signalNb, "signal required") 175 assert.Equal(t, test.initExpected, initNb, "init required") 176 assert.Equal(t, test.updateTables, updatedTables, "tables to update") 177 }) 178 } 179 } 180 181 // TestViewUpdate tests the update of the view handled as expected. 182 func TestViewsUpdates(t *testing.T) { 183 // receives a input from a shard and what views are updated. 184 type input struct { 185 shard string 186 viewUpdates []string 187 } 188 // test will receive multiple inputs and then find the consolidated view updates. 189 // if able to receive results successfully, then signal is made to the consumer. 190 type testCase struct { 191 desc string 192 updateViews []string 193 signalExpected, initExpected int 194 inputs []input 195 delay time.Duration // this causes a delay between inputs 196 init, initFail, updateFail bool 197 } 198 tests := []testCase{{ 199 desc: "received same view updates from shards", 200 inputs: []input{{shard: "-80", viewUpdates: []string{"a"}}, {shard: "80-", viewUpdates: []string{"a"}}}, 201 updateViews: []string{"a"}, 202 signalExpected: 1, 203 }, { 204 desc: "received different view updates from shards", 205 inputs: []input{{shard: "0", viewUpdates: []string{"a"}}, {shard: "0", viewUpdates: []string{"b"}}}, 206 updateViews: []string{"a", "b"}, 207 signalExpected: 1, 208 }, { 209 desc: "delay between inputs - different signals from each input", 210 inputs: []input{{shard: "0", viewUpdates: []string{"a"}}, {shard: "0", viewUpdates: []string{"b"}}}, 211 updateViews: []string{"b"}, 212 signalExpected: 2, 213 delay: 10 * time.Millisecond, 214 }, { 215 desc: "no change - no signal", 216 inputs: []input{{shard: "0"}, {shard: "0"}}, 217 }, { 218 desc: "initialization did not happen - full views load over only updated views", 219 inputs: []input{{shard: "-80", viewUpdates: []string{"a"}}, {shard: "80-", viewUpdates: []string{"a"}}}, 220 signalExpected: 1, 221 initExpected: 1, 222 init: true, 223 }, { 224 desc: "initialization did not happen - full view load failed - no signal", 225 inputs: []input{{shard: "-80", viewUpdates: []string{"a"}}, {shard: "80-", viewUpdates: []string{"a"}}}, 226 signalExpected: 0, 227 initExpected: 1, 228 init: true, 229 initFail: true, 230 }, { 231 desc: "updated views failed - no signal", 232 inputs: []input{{shard: "-80", viewUpdates: []string{"a"}}, {shard: "80-", viewUpdates: []string{"b"}}}, 233 updateViews: []string{"a", "b"}, 234 signalExpected: 0, 235 updateFail: true, 236 }, 237 } 238 for i, test := range tests { 239 t.Run(fmt.Sprintf("%d", i+1), func(t *testing.T) { 240 var signalNb, initNb int 241 var updatedViews []string 242 update := func(th *discovery.TabletHealth) bool { 243 updatedViews = th.Stats.ViewSchemaChanged 244 return !test.updateFail 245 } 246 signal := func() { 247 signalNb++ 248 } 249 updateCont := updateController{ 250 update: update, 251 signal: signal, 252 consumeDelay: 5 * time.Millisecond, 253 reloadKeyspace: func(th *discovery.TabletHealth) error { 254 initNb++ 255 var err error 256 if test.initFail { 257 err = fmt.Errorf("error") 258 } 259 return err 260 }, 261 loaded: !test.init, 262 } 263 264 for _, in := range test.inputs { 265 target := &querypb.Target{ 266 Keyspace: "ks", 267 Shard: in.shard, 268 TabletType: topodatapb.TabletType_PRIMARY, 269 } 270 tablet := &topodatapb.Tablet{ 271 Keyspace: target.Keyspace, 272 Shard: target.Shard, 273 Type: target.TabletType, 274 } 275 d := &discovery.TabletHealth{ 276 Tablet: tablet, 277 Target: target, 278 Serving: true, 279 Stats: &querypb.RealtimeStats{ViewSchemaChanged: in.viewUpdates}, 280 } 281 if test.delay > 0 { 282 time.Sleep(test.delay) 283 } 284 updateCont.add(d) 285 } 286 287 for { 288 updateCont.mu.Lock() 289 done := updateCont.queue == nil 290 updateCont.mu.Unlock() 291 if done { 292 break 293 } 294 } 295 296 assert.Equal(t, test.signalExpected, signalNb, "signal required") 297 assert.Equal(t, test.initExpected, initNb, "init required") 298 assert.Equal(t, test.updateViews, updatedViews, "views to update") 299 }) 300 } 301 }