vitess.io/vitess@v0.16.2/go/vt/vtgr/controller/group_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 controller 18 19 import ( 20 "math" 21 "testing" 22 23 "vitess.io/vitess/go/vt/vtgr/log" 24 25 "vitess.io/vitess/go/vt/vtgr/db" 26 "vitess.io/vitess/go/vt/vtgr/inst" 27 28 "github.com/stretchr/testify/assert" 29 ) 30 31 func TestSQLGroupToString(t *testing.T) { 32 group := NewSQLGroup(2, true, "ks", "0") 33 v1 := db.NewGroupView("v1", "host1", 10) 34 v1.GroupName = "group_name" 35 var l1 []*db.GroupMember 36 var l2 []*db.GroupMember 37 m1 := db.NewGroupMember("ONLINE", "PRIMARY", "host1", 10, false) 38 m2 := db.NewGroupMember("ONLINE", "SECONDARY", "host2", 10, true) 39 m3 := db.NewGroupMember("OFFLINE", "SECONDARY", "host3", 10, true) 40 l1 = append(l1, m1) 41 l1 = append(l1, m2) 42 v1.UnresolvedMembers = l1 43 l2 = append(l2, m3) 44 v2 := db.NewGroupView("v2", "host2", 10) 45 v2.GroupName = "group_name" 46 v2.UnresolvedMembers = l2 47 group.recordView(v2) 48 group.recordView(v1) 49 assert.Equal(t, `[v2] SQLGroup group=group_name | host3 SECONDARY OFFLINE readonly=true 50 [v1] SQLGroup group=group_name | host1 PRIMARY ONLINE readonly=false | host2 SECONDARY ONLINE readonly=true 51 `, group.ToString()) 52 group.Resolve() 53 assert.Equal(t, `[v2] SQLGroup group=group_name | host3 SECONDARY OFFLINE readonly=true 54 [v1] SQLGroup group=group_name | host1 PRIMARY ONLINE readonly=false | host2 SECONDARY ONLINE readonly=true 55 [resolved_view] 56 group_name=group_name 57 [host1] state=ONLINE role=PRIMARY readonly=false 58 [host2] state=ONLINE role=SECONDARY readonly=true 59 [host3] state=OFFLINE role=SECONDARY readonly=true 60 `, group.ToString()) 61 } 62 63 func TestGetGroupName(t *testing.T) { 64 group := NewSQLGroup(3, true, "ks", "0") 65 v1 := db.NewGroupView("v1", "host1", 10) 66 v1.GroupName = "group" 67 v1.UnresolvedMembers = []*db.GroupMember{ 68 db.NewGroupMember("OFFLINE", "", "host1", 10, true), 69 } 70 group.recordView(v1) 71 v2 := db.NewGroupView("v2", "host2", 10) 72 v2.GroupName = "group" 73 v2.UnresolvedMembers = []*db.GroupMember{ 74 db.NewGroupMember("OFFLINE", "", "", 0, true), 75 } 76 group.recordView(v2) 77 err := group.Resolve() 78 assert.NoError(t, err) 79 name := group.GetGroupName() 80 assert.Equal(t, "group", name) 81 v3 := db.NewGroupView("v3", "host3", 10) 82 v3.GroupName = "group_foo" 83 group.recordView(v3) 84 err = group.Resolve() 85 assert.Errorf(t, err, "group has split brain") 86 name = group.GetGroupName() 87 // group keeps the group name before finding a divergent group name 88 assert.Equal(t, "group", name) 89 } 90 91 func TestIsActiveWithMultiplePrimary(t *testing.T) { 92 group := NewSQLGroup(2, true, "ks", "0") 93 v1 := db.NewGroupView("v1", "host1", 10) 94 v1.GroupName = "group" 95 v1.UnresolvedMembers = []*db.GroupMember{ 96 db.NewGroupMember("ONLINE", "PRIMARY", "host1", 10, false), 97 db.NewGroupMember("ONLINE", "SECONDARY", "host2", 10, true), 98 } 99 group.recordView(v1) 100 v2 := db.NewGroupView("v2", "host2", 10) 101 v2.GroupName = "group" 102 v2.UnresolvedMembers = []*db.GroupMember{ 103 db.NewGroupMember("ONLINE", "SECONDARY", "host1", 10, true), 104 db.NewGroupMember("ONLINE", "PRIMARY", "host2", 10, false), 105 } 106 group.recordView(v2) 107 err := group.Resolve() 108 assert.Errorf(t, err, "group network partition") 109 } 110 111 func TestIsSafeToBootstrap(t *testing.T) { 112 group := NewSQLGroup(1, true, "ks", "0") 113 isSafe := group.IsSafeToBootstrap() 114 assert.False(t, isSafe) 115 v1 := db.NewGroupView("v1", "host1", 10) 116 v1.GroupName = "group" 117 v1.UnresolvedMembers = []*db.GroupMember{ 118 db.NewGroupMember("OFFLINE", "", "", 0, true), 119 db.NewGroupMember("OFFLINE", "", "", 0, true), 120 } 121 group.recordView(v1) 122 group.Resolve() 123 isSafe = group.IsSafeToBootstrap() 124 assert.True(t, isSafe) 125 } 126 127 func TestIsSafeToBootstrapWithPrimary(t *testing.T) { 128 group := NewSQLGroup(1, true, "ks", "0") 129 v1 := db.NewGroupView("v1", "host1", 10) 130 v1.GroupName = "group" 131 // it is not safe to bootstrap if we see a primary node in group 132 v1.UnresolvedMembers = []*db.GroupMember{ 133 db.NewGroupMember("ONLINE", "PRIMARY", "host1", 0, false), 134 db.NewGroupMember("OFFLINE", "", "", 0, true), 135 } 136 group.recordView(v1) 137 group.Resolve() 138 isSafe := group.IsSafeToBootstrap() 139 assert.False(t, isSafe) 140 } 141 142 func TestIsUnconnectedReplica(t *testing.T) { 143 group := NewSQLGroup(1, true, "ks", "0") 144 isSafe := group.IsSafeToBootstrap() 145 assert.False(t, isSafe) 146 v1 := db.NewGroupView("v1", "host1", 10) 147 v1.GroupName = "group" 148 v1.UnresolvedMembers = []*db.GroupMember{ 149 db.NewGroupMember("ONLINE", "PRIMARY", "host1", 10, false), 150 db.NewGroupMember("ONLINE", "SECONDARY", "host2", 10, true), 151 } 152 group.recordView(v1) 153 group.Resolve() 154 isUnconnected := group.IsUnconnectedReplica(&inst.InstanceKey{Hostname: "host2", Port: 10}) 155 assert.False(t, isUnconnected) 156 } 157 158 func TestGetOnlineGroupSizeFromPrimary(t *testing.T) { 159 group := NewSQLGroup(1, true, "ks", "0") 160 isSafe := group.IsSafeToBootstrap() 161 assert.False(t, isSafe) 162 v1 := db.NewGroupView("v1", "host1", 10) 163 v1.GroupName = "group" 164 v1.UnresolvedMembers = []*db.GroupMember{ 165 db.NewGroupMember("ONLINE", "PRIMARY", "host1", 10, false), 166 db.NewGroupMember("ONLINE", "SECONDARY", "host2", 10, true), 167 db.NewGroupMember("RECOVERING", "SECONDARY", "host3", 10, true), 168 } 169 v2 := db.NewGroupView("v2", "host2", 10) 170 v2.GroupName = "group" 171 v2.UnresolvedMembers = []*db.GroupMember{} 172 group.recordView(v1) 173 group.recordView(v2) 174 group.Resolve() 175 size, readOnly := group.GetOnlineGroupInfo() 176 assert.Equal(t, 2, size) 177 assert.False(t, readOnly) 178 } 179 180 func TestNetworkPartition(t *testing.T) { 181 group := NewSQLGroup(3, true, "ks", "0") 182 v1 := db.NewGroupView("v1", "host1", 10) 183 v1.GroupName = "group" 184 v1.UnresolvedMembers = []*db.GroupMember{ 185 db.NewGroupMember("ONLINE", "PRIMARY", "host1", 10, false), 186 db.NewGroupMember("UNREACHABLE", "SECONDARY", "host2", 10, true), 187 db.NewGroupMember("UNREACHABLE", "SECONDARY", "host3", 10, true), 188 } 189 v2 := db.NewGroupView("v2", "host2", 10) 190 v2.GroupName = "group" 191 v2.UnresolvedMembers = []*db.GroupMember{ 192 db.NewGroupMember("OFFLINE", "", "host2", 10, true), 193 } 194 v3 := db.NewGroupView("v3", "host3", 10) 195 v3.GroupName = "group" 196 v3.UnresolvedMembers = []*db.GroupMember{ 197 db.NewGroupMember("OFFLINE", "", "host3", 10, true), 198 } 199 group.recordView(v1) 200 group.recordView(v2) 201 group.recordView(v3) 202 err := group.Resolve() 203 assert.EqualErrorf(t, err, "group backoff error", err.Error()) 204 rv := group.resolvedView 205 assert.Equal(t, "group", rv.groupName) 206 assert.Equal(t, map[inst.InstanceKey]db.GroupMember{ 207 {Hostname: "host1", Port: 10}: {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 208 {Hostname: "host2", Port: 10}: {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.UNREACHABLE, ReadOnly: true}, 209 {Hostname: "host3", Port: 10}: {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.UNREACHABLE, ReadOnly: true}, 210 }, rv.view) 211 } 212 213 func TestInconsistentState(t *testing.T) { 214 group := NewSQLGroup(3, true, "ks", "0") 215 v1 := db.NewGroupView("v1", "host1", 10) 216 v1.GroupName = "group" 217 v1.HeartbeatStaleness = 11 218 v1.UnresolvedMembers = []*db.GroupMember{ 219 db.NewGroupMember("ONLINE", "PRIMARY", "host1", 10, false), 220 db.NewGroupMember("ONLINE", "SECONDARY", "host2", 10, true), 221 db.NewGroupMember("ONLINE", "SECONDARY", "host3", 10, true), 222 } 223 v2 := db.NewGroupView("v2", "host2", 10) 224 v2.GroupName = "group" 225 v2.HeartbeatStaleness = 11 226 v2.UnresolvedMembers = []*db.GroupMember{ 227 db.NewGroupMember("OFFLINE", "", "host2", 10, true), 228 } 229 v3 := db.NewGroupView("v3", "host3", 10) 230 v3.GroupName = "group" 231 v3.HeartbeatStaleness = 13 232 v3.UnresolvedMembers = []*db.GroupMember{ 233 db.NewGroupMember("OFFLINE", "", "host3", 10, true), 234 } 235 group.recordView(v1) 236 group.recordView(v2) 237 group.recordView(v3) 238 group.heartbeatThreshold = 10 239 err := group.Resolve() 240 assert.EqualErrorf(t, err, "group backoff error", err.Error()) 241 rv := group.resolvedView 242 assert.Equal(t, "group", rv.groupName) 243 assert.Nil(t, rv.view) 244 } 245 246 func TestInconsistentStateWithInvalidStaleResult(t *testing.T) { 247 group := NewSQLGroup(3, true, "ks", "0") 248 v1 := db.NewGroupView("v1", "host1", 10) 249 v1.GroupName = "group" 250 v1.HeartbeatStaleness = math.MaxInt32 251 v1.UnresolvedMembers = []*db.GroupMember{ 252 db.NewGroupMember("ONLINE", "PRIMARY", "host1", 10, false), 253 db.NewGroupMember("ONLINE", "SECONDARY", "host2", 10, true), 254 db.NewGroupMember("ONLINE", "SECONDARY", "host3", 10, true), 255 } 256 v2 := db.NewGroupView("v2", "host2", 10) 257 v2.GroupName = "group" 258 v2.HeartbeatStaleness = math.MaxInt32 259 v2.UnresolvedMembers = []*db.GroupMember{ 260 db.NewGroupMember("OFFLINE", "", "host2", 10, true), 261 } 262 v3 := db.NewGroupView("v3", "host3", 10) 263 v3.GroupName = "group" 264 v3.HeartbeatStaleness = math.MaxInt32 265 v3.UnresolvedMembers = []*db.GroupMember{ 266 db.NewGroupMember("OFFLINE", "", "host3", 10, true), 267 } 268 group.recordView(v1) 269 group.recordView(v2) 270 group.recordView(v3) 271 group.heartbeatThreshold = 10 272 err := group.Resolve() 273 // Same setup as TestInconsistentState but because HeartbeatStaleness are all MaxInt32 274 // the backoff is not triggered 275 assert.NoError(t, err) 276 rv := group.resolvedView 277 assert.Equal(t, "group", rv.groupName) 278 } 279 280 func TestInconsistentUnknownState(t *testing.T) { 281 group := NewSQLGroup(3, true, "ks", "0") 282 v1 := db.NewGroupView("v1", "host1", 10) 283 v1.GroupName = "group" 284 v1.UnresolvedMembers = []*db.GroupMember{ 285 db.NewGroupMember("ONLINE", "PRIMARY", "host1", 10, false), 286 db.NewGroupMember("RECOVERING", "SECONDARY", "host2", 10, true), 287 db.NewGroupMember("ONLINE", "SECONDARY", "host3", 10, true), 288 } 289 v2 := db.NewGroupView("v2", "host2", 10) 290 v2.GroupName = "group" 291 v2.UnresolvedMembers = []*db.GroupMember{ 292 db.NewGroupMember("", "", "host2", 10, true), 293 } 294 v3 := db.NewGroupView("v3", "host3", 10) 295 v3.GroupName = "group" 296 v3.UnresolvedMembers = []*db.GroupMember{ 297 db.NewGroupMember("ONLINE", "SECONDARY", "host3", 10, true), 298 } 299 group.recordView(v1) 300 group.recordView(v2) 301 group.recordView(v3) 302 err := group.Resolve() 303 // host 2 reports itself with empty state 304 // therefore we shouldn't raise error even with inconsistent state 305 assert.NoError(t, err) 306 rv := group.resolvedView 307 assert.Equal(t, "group", rv.groupName) 308 assert.Equal(t, map[inst.InstanceKey]db.GroupMember{ 309 {Hostname: "host1", Port: 10}: {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 310 {Hostname: "host2", Port: 10}: {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.RECOVERING, ReadOnly: true}, 311 {Hostname: "host3", Port: 10}: {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 312 }, rv.view) 313 } 314 315 func TestIsBootstrapInProcess(t *testing.T) { 316 group := NewSQLGroup(3, true, "ks", "0") 317 v1 := db.NewGroupView("v1", "host1", 10) 318 v1.GroupName = "group" 319 v1.UnresolvedMembers = []*db.GroupMember{ 320 db.NewGroupMember("RECOVERING", "SECONDARY", "host1", 10, false), 321 } 322 v2 := db.NewGroupView("v2", "host2", 10) 323 v2.GroupName = "group" 324 v2.UnresolvedMembers = []*db.GroupMember{ 325 db.NewGroupMember("OFFLINE", "", "host2", 10, false), 326 } 327 v3 := db.NewGroupView("v3", "host", 10) 328 v3.GroupName = "group" 329 v3.UnresolvedMembers = []*db.GroupMember{} 330 group.recordView(v1) 331 group.recordView(v2) 332 group.recordView(v3) 333 err := group.Resolve() 334 assert.Errorf(t, err, "group transient error") 335 } 336 337 func TestResolve(t *testing.T) { 338 healthyView := []*db.GroupMember{ 339 {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 340 {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 341 {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 342 } 343 var testCases = []struct { 344 testName string 345 views []*db.GroupView 346 expected *ResolvedView 347 errorMsg string 348 }{ 349 {"test healthy shard", []*db.GroupView{ 350 {MySQLHost: "host1", MySQLPort: 10, GroupName: "group", UnresolvedMembers: healthyView}, 351 {MySQLHost: "host2", MySQLPort: 10, GroupName: "group", UnresolvedMembers: healthyView}, 352 {MySQLHost: "host3", MySQLPort: 10, GroupName: "group", UnresolvedMembers: healthyView}, 353 }, &ResolvedView{"group", map[inst.InstanceKey]db.GroupMember{ 354 {Hostname: "host1", Port: 10}: {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 355 {Hostname: "host2", Port: 10}: {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 356 {Hostname: "host3", Port: 10}: {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 357 }, nil}, ""}, 358 {"test readonly with unreachable primary", []*db.GroupView{ // host1 is unreachable 359 {MySQLHost: "host2", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 360 {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 361 {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 362 {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: false}, 363 }}, 364 {MySQLHost: "host3", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 365 {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 366 {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: false}, 367 {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 368 }}, 369 }, &ResolvedView{"group", map[inst.InstanceKey]db.GroupMember{ 370 {Hostname: "host1", Port: 10}: {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 371 {Hostname: "host2", Port: 10}: {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 372 {Hostname: "host3", Port: 10}: {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 373 }, nil}, ""}, 374 {"test split brain by group name", []*db.GroupView{ 375 {MySQLHost: "host1", MySQLPort: 10, GroupName: "group", UnresolvedMembers: healthyView}, 376 {MySQLHost: "host2", MySQLPort: 10, GroupName: "group1", UnresolvedMembers: healthyView}, 377 {MySQLHost: "host3", MySQLPort: 10, GroupName: "group", UnresolvedMembers: healthyView}, 378 }, nil, "group has split brain"}, 379 {"test empty hostname", []*db.GroupView{ 380 {MySQLHost: "host1", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 381 {HostName: "", Port: 0, Role: db.UNKNOWNROLE, State: db.OFFLINE, ReadOnly: true}, 382 }}, 383 {MySQLHost: "host2", MySQLPort: 10, GroupName: "", UnresolvedMembers: []*db.GroupMember{ 384 {HostName: "host2", Port: 10, Role: db.UNKNOWNROLE, State: db.OFFLINE, ReadOnly: true}, 385 }}, 386 {MySQLHost: "host3", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 387 {HostName: "host3", Port: 10, Role: db.UNKNOWNROLE, State: db.OFFLINE, ReadOnly: true}, 388 }}, 389 }, &ResolvedView{"group", map[inst.InstanceKey]db.GroupMember{ 390 {Hostname: "host1", Port: 10}: {HostName: "host1", Port: 10, Role: db.UNKNOWNROLE, State: db.OFFLINE, ReadOnly: true}, 391 {Hostname: "host2", Port: 10}: {HostName: "host2", Port: 10, Role: db.UNKNOWNROLE, State: db.OFFLINE, ReadOnly: true}, 392 {Hostname: "host3", Port: 10}: {HostName: "host3", Port: 10, Role: db.UNKNOWNROLE, State: db.OFFLINE, ReadOnly: true}, 393 }, nil}, ""}, 394 {"test network partition by majority unreachable", []*db.GroupView{ 395 {MySQLHost: "host1", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 396 {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.UNREACHABLE, ReadOnly: false}, 397 {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 398 {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.UNREACHABLE, ReadOnly: true}, 399 }}, 400 }, nil, "group backoff error"}, 401 {"test no network partition with less then majority unreachable", []*db.GroupView{ 402 {MySQLHost: "host1", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 403 {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 404 {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 405 {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.UNREACHABLE, ReadOnly: false}, 406 }}, 407 {MySQLHost: "host2", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 408 {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 409 {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 410 {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.UNREACHABLE, ReadOnly: false}, 411 }}, 412 }, &ResolvedView{"group", map[inst.InstanceKey]db.GroupMember{ 413 {Hostname: "host1", Port: 10}: {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.ONLINE, ReadOnly: false}, 414 {Hostname: "host2", Port: 10}: {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE, ReadOnly: true}, 415 {Hostname: "host3", Port: 10}: {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.UNREACHABLE, ReadOnly: false}, 416 }, nil}, "group backoff error"}, 417 {"test network partition by unreachable primary", []*db.GroupView{ 418 {MySQLHost: "host2", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 419 {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.UNREACHABLE}, 420 {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE}, 421 {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.ONLINE}, 422 }}, 423 {MySQLHost: "host3", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 424 {HostName: "host1", Port: 10, Role: db.PRIMARY, State: db.UNREACHABLE}, 425 {HostName: "host2", Port: 10, Role: db.SECONDARY, State: db.ONLINE}, 426 {HostName: "host3", Port: 10, Role: db.SECONDARY, State: db.ONLINE}, 427 }}, 428 }, nil, "group backoff error"}, 429 {"test bootstrap ongoing", []*db.GroupView{ 430 {MySQLHost: "host1", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{ 431 {HostName: "", Port: 0, Role: db.SECONDARY, State: db.RECOVERING, ReadOnly: true}, 432 }}, 433 {MySQLHost: "host2", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{}}, 434 {MySQLHost: "host3", MySQLPort: 10, GroupName: "group", UnresolvedMembers: []*db.GroupMember{}}, 435 }, nil, "group ongoing bootstrap"}, 436 } 437 for _, testCase := range testCases { 438 t.Run(testCase.testName, func(t *testing.T) { 439 group := SQLGroup{views: testCase.views, statsTags: []string{"ks", "0"}, logger: log.NewVTGRLogger("ks", "0")} 440 err := group.Resolve() 441 if testCase.errorMsg != "" { 442 assert.EqualError(t, err, testCase.errorMsg) 443 } else { 444 assert.NoError(t, err) 445 } 446 if testCase.expected != nil { 447 rv := group.resolvedView 448 expected := testCase.expected 449 assert.Equal(t, expected.view, rv.view) 450 assert.Equal(t, expected.groupName, rv.groupName) 451 } 452 }) 453 } 454 }