github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/migrationminion/worker_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migrationminion_test 5 6 import ( 7 "reflect" 8 "sync" 9 "time" 10 11 "github.com/juju/clock/testclock" 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 "github.com/juju/names/v5" 15 jujutesting "github.com/juju/testing" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/worker/v3" 18 "github.com/juju/worker/v3/workertest" 19 gc "gopkg.in/check.v1" 20 21 "github.com/juju/juju/agent" 22 "github.com/juju/juju/api" 23 "github.com/juju/juju/api/base" 24 apiservererrors "github.com/juju/juju/apiserver/errors" 25 "github.com/juju/juju/core/migration" 26 "github.com/juju/juju/core/network" 27 "github.com/juju/juju/core/watcher" 28 coretesting "github.com/juju/juju/testing" 29 "github.com/juju/juju/worker/fortress" 30 "github.com/juju/juju/worker/migrationminion" 31 ) 32 33 var ( 34 modelTag = names.NewModelTag("model-uuid") 35 addrs = []string{"1.1.1.1:1111", "2.2.2.2:2222"} 36 agentTag = names.NewMachineTag("42") 37 agentPassword = "sekret" 38 caCert = "cert" 39 ) 40 41 type Suite struct { 42 coretesting.BaseSuite 43 config migrationminion.Config 44 stub *jujutesting.Stub 45 client *stubMinionClient 46 guard *stubGuard 47 agent *stubAgent 48 clock *testclock.Clock 49 } 50 51 var _ = gc.Suite(&Suite{}) 52 53 func (s *Suite) SetUpTest(c *gc.C) { 54 s.BaseSuite.SetUpTest(c) 55 s.stub = new(jujutesting.Stub) 56 s.client = newStubMinionClient(s.stub) 57 s.guard = newStubGuard(s.stub) 58 s.agent = newStubAgent() 59 s.clock = testclock.NewClock(time.Now()) 60 s.config = migrationminion.Config{ 61 Facade: s.client, 62 Guard: s.guard, 63 Agent: s.agent, 64 Clock: s.clock, 65 APIOpen: s.apiOpen, 66 ValidateMigration: func(base.APICaller) error { 67 s.stub.AddCall("ValidateMigration") 68 return nil 69 }, 70 Logger: loggo.GetLogger("test"), 71 } 72 } 73 74 func (s *Suite) apiOpen(info *api.Info, _ api.DialOpts) (api.Connection, error) { 75 s.stub.AddCall("API open", info) 76 return &stubConnection{stub: s.stub}, nil 77 } 78 79 func (s *Suite) TestStartAndStop(c *gc.C) { 80 w, err := migrationminion.New(s.config) 81 c.Assert(err, jc.ErrorIsNil) 82 workertest.CleanKill(c, w) 83 s.stub.CheckCallNames(c, "Watch") 84 } 85 86 func (s *Suite) TestWatchFailure(c *gc.C) { 87 s.client.watchErr = errors.New("boom") 88 w, err := migrationminion.New(s.config) 89 c.Assert(err, jc.ErrorIsNil) 90 err = workertest.CheckKilled(c, w) 91 c.Check(err, gc.ErrorMatches, "setting up watcher: boom") 92 } 93 94 func (s *Suite) TestClosedWatcherChannel(c *gc.C) { 95 close(s.client.watcher.changes) 96 w, err := migrationminion.New(s.config) 97 c.Assert(err, jc.ErrorIsNil) 98 err = workertest.CheckKilled(c, w) 99 c.Check(err, gc.ErrorMatches, "watcher channel closed") 100 } 101 102 func (s *Suite) TestUnlockError(c *gc.C) { 103 s.client.watcher.changes <- watcher.MigrationStatus{ 104 Phase: migration.NONE, 105 } 106 s.guard.unlockErr = errors.New("squish") 107 w, err := migrationminion.New(s.config) 108 c.Assert(err, jc.ErrorIsNil) 109 110 err = workertest.CheckKilled(c, w) 111 c.Check(err, gc.ErrorMatches, "squish") 112 s.stub.CheckCallNames(c, "Watch", "Unlock") 113 } 114 115 func (s *Suite) TestLockdownError(c *gc.C) { 116 s.client.watcher.changes <- watcher.MigrationStatus{ 117 Phase: migration.QUIESCE, 118 } 119 s.guard.lockdownErr = errors.New("squash") 120 w, err := migrationminion.New(s.config) 121 c.Assert(err, jc.ErrorIsNil) 122 123 err = workertest.CheckKilled(c, w) 124 c.Check(err, gc.ErrorMatches, "squash") 125 s.stub.CheckCallNames(c, "Watch", "Lockdown") 126 } 127 128 func (s *Suite) TestNonRunningPhases(c *gc.C) { 129 phases := []migration.Phase{ 130 migration.UNKNOWN, 131 migration.NONE, 132 migration.LOGTRANSFER, 133 migration.REAP, 134 migration.REAPFAILED, 135 migration.DONE, 136 migration.ABORT, 137 migration.ABORTDONE, 138 } 139 for _, phase := range phases { 140 s.checkNonRunningPhase(c, phase) 141 } 142 } 143 144 func (s *Suite) checkNonRunningPhase(c *gc.C, phase migration.Phase) { 145 c.Logf("checking %s", phase) 146 s.stub.ResetCalls() 147 s.client.watcher.changes <- watcher.MigrationStatus{Phase: phase} 148 w, err := migrationminion.New(s.config) 149 c.Assert(err, jc.ErrorIsNil) 150 workertest.CheckAlive(c, w) 151 workertest.CleanKill(c, w) 152 s.stub.CheckCallNames(c, "Watch", "Unlock") 153 } 154 155 func (s *Suite) TestQUIESCE(c *gc.C) { 156 s.client.watcher.changes <- watcher.MigrationStatus{ 157 MigrationId: "id", 158 Phase: migration.QUIESCE, 159 } 160 w, err := migrationminion.New(s.config) 161 c.Assert(err, jc.ErrorIsNil) 162 defer workertest.CleanKill(c, w) 163 164 s.waitForStubCalls(c, []string{ 165 "Watch", 166 "Lockdown", 167 "Report", 168 }) 169 s.stub.CheckCall(c, 2, "Report", "id", migration.QUIESCE, true) 170 } 171 172 func (s *Suite) TestVALIDATION(c *gc.C) { 173 s.client.watcher.changes <- watcher.MigrationStatus{ 174 MigrationId: "id", 175 Phase: migration.VALIDATION, 176 TargetAPIAddrs: addrs, 177 TargetCACert: caCert, 178 } 179 w, err := migrationminion.New(s.config) 180 c.Assert(err, jc.ErrorIsNil) 181 defer workertest.CleanKill(c, w) 182 183 s.waitForStubCalls(c, []string{ 184 "Watch", 185 "Lockdown", 186 "API open", 187 "ValidateMigration", 188 "API close", 189 "Report", 190 }) 191 s.stub.CheckCall(c, 2, "API open", &api.Info{ 192 ModelTag: modelTag, 193 Tag: agentTag, 194 Password: agentPassword, 195 Addrs: addrs, 196 CACert: caCert, 197 }) 198 s.stub.CheckCall(c, 5, "Report", "id", migration.VALIDATION, true) 199 } 200 201 func (s *Suite) TestVALIDATIONCantConnect(c *gc.C) { 202 s.client.watcher.changes <- watcher.MigrationStatus{ 203 MigrationId: "id", 204 Phase: migration.VALIDATION, 205 } 206 s.config.APIOpen = func(*api.Info, api.DialOpts) (api.Connection, error) { 207 s.stub.AddCall("API open") 208 return nil, errors.New("boom") 209 } 210 w, err := migrationminion.New(s.config) 211 c.Assert(err, jc.ErrorIsNil) 212 defer workertest.CleanKill(c, w) 213 214 // Advance time enough for all of the retries to be exhausted. 215 sleepTime := 100 * time.Millisecond 216 for i := 0; i < 9; i++ { 217 err := s.clock.WaitAdvance(sleepTime, coretesting.ShortWait, 1) 218 c.Assert(err, jc.ErrorIsNil) 219 sleepTime = (16 * sleepTime) / 10 220 } 221 222 s.waitForStubCalls(c, []string{ 223 "Watch", 224 "Lockdown", 225 "API open", 226 "API open", 227 "API open", 228 "API open", 229 "API open", 230 "API open", 231 "API open", 232 "API open", 233 "API open", 234 "API open", 235 "Report", 236 }) 237 s.stub.CheckCall(c, 12, "Report", "id", migration.VALIDATION, false) 238 } 239 240 func (s *Suite) TestVALIDATIONCantConnectNotReportForTryAgainError(c *gc.C) { 241 s.client.watcher.changes <- watcher.MigrationStatus{ 242 MigrationId: "id", 243 Phase: migration.VALIDATION, 244 } 245 s.config.APIOpen = func(*api.Info, api.DialOpts) (api.Connection, error) { 246 s.stub.AddCall("API open") 247 return nil, apiservererrors.ErrTryAgain 248 } 249 w, err := migrationminion.New(s.config) 250 c.Assert(err, jc.ErrorIsNil) 251 defer workertest.CleanKill(c, w) 252 253 // Advance time enough for all of the retries to be exhausted. 254 sleepTime := 100 * time.Millisecond 255 for i := 0; i < 9; i++ { 256 err := s.clock.WaitAdvance(sleepTime, coretesting.ShortWait, 1) 257 c.Assert(err, jc.ErrorIsNil) 258 sleepTime = (16 * sleepTime) / 10 259 } 260 261 s.waitForStubCalls(c, []string{ 262 "Watch", 263 "Lockdown", 264 "API open", 265 "API open", 266 "API open", 267 "API open", 268 "API open", 269 "API open", 270 "API open", 271 "API open", 272 "API open", 273 "API open", 274 }) 275 } 276 277 func (s *Suite) TestVALIDATIONFail(c *gc.C) { 278 s.client.watcher.changes <- watcher.MigrationStatus{ 279 MigrationId: "id", 280 Phase: migration.VALIDATION, 281 } 282 s.config.ValidateMigration = func(base.APICaller) error { 283 s.stub.AddCall("ValidateMigration") 284 return errors.New("boom") 285 } 286 w, err := migrationminion.New(s.config) 287 c.Assert(err, jc.ErrorIsNil) 288 defer workertest.CleanKill(c, w) 289 290 // Advance time enough for all of the retries to be exhausted. 291 sleepTime := 100 * time.Millisecond 292 for i := 0; i < 9; i++ { 293 err := s.clock.WaitAdvance(sleepTime, coretesting.ShortWait, 1) 294 c.Assert(err, jc.ErrorIsNil) 295 sleepTime = (16 * sleepTime) / 10 296 } 297 298 expectedCalls := []string{"Watch", "Lockdown"} 299 for i := 0; i < 10; i++ { 300 expectedCalls = append(expectedCalls, "API open", "ValidateMigration", "API close") 301 } 302 expectedCalls = append(expectedCalls, "Report") 303 s.waitForStubCalls(c, expectedCalls) 304 s.stub.CheckCall(c, 32, "Report", "id", migration.VALIDATION, false) 305 } 306 307 func (s *Suite) TestVALIDATIONRetrySucceed(c *gc.C) { 308 s.client.watcher.changes <- watcher.MigrationStatus{ 309 MigrationId: "id", 310 Phase: migration.VALIDATION, 311 } 312 var stub jujutesting.Stub 313 stub.SetErrors(errors.New("nope"), errors.New("not yet"), nil) 314 s.config.ValidateMigration = func(base.APICaller) error { 315 stub.AddCall("ValidateMigration") 316 return stub.NextErr() 317 } 318 319 w, err := migrationminion.New(s.config) 320 c.Assert(err, jc.ErrorIsNil) 321 defer workertest.CleanKill(c, w) 322 323 waitForStubCalls(c, &stub, "ValidateMigration") 324 325 err = s.clock.WaitAdvance(100*time.Millisecond, coretesting.LongWait, 1) 326 c.Assert(err, jc.ErrorIsNil) 327 328 waitForStubCalls(c, &stub, "ValidateMigration", "ValidateMigration") 329 330 err = s.clock.WaitAdvance(160*time.Millisecond, coretesting.LongWait, 1) 331 c.Assert(err, jc.ErrorIsNil) 332 333 s.waitForStubCalls(c, []string{ 334 "Watch", 335 "Lockdown", 336 "API open", 337 "API close", 338 "API open", 339 "API close", 340 "API open", 341 "API close", 342 "Report", 343 }) 344 s.stub.CheckCall(c, 8, "Report", "id", migration.VALIDATION, true) 345 } 346 347 func (s *Suite) TestSUCCESS(c *gc.C) { 348 s.client.watcher.changes <- watcher.MigrationStatus{ 349 MigrationId: "id", 350 Phase: migration.SUCCESS, 351 TargetAPIAddrs: addrs, 352 TargetCACert: caCert, 353 } 354 w, err := migrationminion.New(s.config) 355 c.Assert(err, jc.ErrorIsNil) 356 357 select { 358 case <-s.agent.configChanged: 359 case <-time.After(coretesting.LongWait): 360 c.Fatal("timed out") 361 } 362 workertest.CleanKill(c, w) 363 c.Assert(s.agent.conf.addrs, gc.DeepEquals, addrs) 364 c.Assert(s.agent.conf.caCert, gc.DeepEquals, caCert) 365 s.stub.CheckCallNames(c, "Watch", "Lockdown", "Report") 366 s.stub.CheckCall(c, 2, "Report", "id", migration.SUCCESS, true) 367 } 368 369 func (s *Suite) waitForStubCalls(c *gc.C, expectedCallNames []string) { 370 waitForStubCalls(c, s.stub, expectedCallNames...) 371 } 372 373 func waitForStubCalls(c *gc.C, stub *jujutesting.Stub, expectedCallNames ...string) { 374 var callNames []string 375 for a := coretesting.LongAttempt.Start(); a.Next(); { 376 callNames = stubCallNames(stub) 377 if reflect.DeepEqual(callNames, expectedCallNames) { 378 return 379 } 380 } 381 c.Fatalf("failed to see expected calls. saw: %v", callNames) 382 } 383 384 // Make this a feature of stub 385 func stubCallNames(stub *jujutesting.Stub) []string { 386 var out []string 387 for _, call := range stub.Calls() { 388 out = append(out, call.FuncName) 389 } 390 return out 391 } 392 393 func newStubGuard(stub *jujutesting.Stub) *stubGuard { 394 return &stubGuard{stub: stub} 395 } 396 397 type stubGuard struct { 398 stub *jujutesting.Stub 399 unlockErr error 400 lockdownErr error 401 } 402 403 func (g *stubGuard) Lockdown(fortress.Abort) error { 404 g.stub.AddCall("Lockdown") 405 return g.lockdownErr 406 } 407 408 func (g *stubGuard) Unlock() error { 409 g.stub.AddCall("Unlock") 410 return g.unlockErr 411 } 412 413 func newStubMinionClient(stub *jujutesting.Stub) *stubMinionClient { 414 return &stubMinionClient{ 415 stub: stub, 416 watcher: newStubWatcher(), 417 } 418 } 419 420 type stubMinionClient struct { 421 stub *jujutesting.Stub 422 watcher *stubWatcher 423 watchErr error 424 } 425 426 func (c *stubMinionClient) Watch() (watcher.MigrationStatusWatcher, error) { 427 c.stub.MethodCall(c, "Watch") 428 if c.watchErr != nil { 429 return nil, c.watchErr 430 } 431 return c.watcher, nil 432 } 433 434 func (c *stubMinionClient) Report(id string, phase migration.Phase, success bool) error { 435 c.stub.MethodCall(c, "Report", id, phase, success) 436 return nil 437 } 438 439 func newStubWatcher() *stubWatcher { 440 return &stubWatcher{ 441 Worker: workertest.NewErrorWorker(nil), 442 changes: make(chan watcher.MigrationStatus, 1), 443 } 444 } 445 446 type stubWatcher struct { 447 worker.Worker 448 changes chan watcher.MigrationStatus 449 } 450 451 func (w *stubWatcher) Changes() <-chan watcher.MigrationStatus { 452 return w.changes 453 } 454 455 func newStubAgent() *stubAgent { 456 return &stubAgent{ 457 configChanged: make(chan bool), 458 } 459 } 460 461 type stubAgent struct { 462 agent.Agent 463 configChanged chan bool 464 conf stubAgentConfig 465 } 466 467 func (ma *stubAgent) CurrentConfig() agent.Config { 468 return &ma.conf 469 } 470 471 func (ma *stubAgent) ChangeConfig(f agent.ConfigMutator) error { 472 defer close(ma.configChanged) 473 return f(&ma.conf) 474 } 475 476 type stubAgentConfig struct { 477 agent.ConfigSetter 478 479 mu sync.Mutex 480 addrs []string 481 caCert string 482 } 483 484 func (mc *stubAgentConfig) SetAPIHostPorts(servers []network.HostPorts) error { 485 mc.mu.Lock() 486 defer mc.mu.Unlock() 487 mc.addrs = nil 488 for _, hps := range servers { 489 for _, hp := range hps { 490 mc.addrs = append(mc.addrs, network.DialAddress(hp)) 491 } 492 } 493 return nil 494 } 495 496 func (mc *stubAgentConfig) SetCACert(cert string) { 497 mc.mu.Lock() 498 defer mc.mu.Unlock() 499 mc.caCert = cert 500 } 501 502 func (mc *stubAgentConfig) APIInfo() (*api.Info, bool) { 503 mc.mu.Lock() 504 defer mc.mu.Unlock() 505 return &api.Info{ 506 Addrs: mc.addrs, 507 CACert: mc.caCert, 508 ModelTag: modelTag, 509 Tag: agentTag, 510 Password: agentPassword, 511 }, true 512 } 513 514 type stubConnection struct { 515 api.Connection 516 stub *jujutesting.Stub 517 } 518 519 func (c *stubConnection) Close() error { 520 c.stub.AddCall("API close") 521 return nil 522 }