github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/uniter/resolver_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter_test 5 6 import ( 7 "fmt" 8 9 "github.com/juju/charm/v12/hooks" 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/names/v5" 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 17 "github.com/juju/juju/core/model" 18 "github.com/juju/juju/rpc/params" 19 "github.com/juju/juju/worker/uniter" 20 uniteractions "github.com/juju/juju/worker/uniter/actions" 21 unitercharm "github.com/juju/juju/worker/uniter/charm" 22 "github.com/juju/juju/worker/uniter/container" 23 "github.com/juju/juju/worker/uniter/hook" 24 "github.com/juju/juju/worker/uniter/leadership" 25 "github.com/juju/juju/worker/uniter/operation" 26 "github.com/juju/juju/worker/uniter/reboot" 27 "github.com/juju/juju/worker/uniter/remotestate" 28 "github.com/juju/juju/worker/uniter/resolver" 29 "github.com/juju/juju/worker/uniter/secrets" 30 "github.com/juju/juju/worker/uniter/storage" 31 "github.com/juju/juju/worker/uniter/upgradeseries" 32 "github.com/juju/juju/worker/uniter/verifycharmprofile" 33 ) 34 35 type baseResolverSuite struct { 36 stub testing.Stub 37 charmURL string 38 remoteState remotestate.Snapshot 39 opFactory operation.Factory 40 resolver resolver.Resolver 41 resolverConfig uniter.ResolverConfig 42 43 clearResolved func() error 44 reportHookError func(hook.Info) error 45 46 workloadEvents container.WorkloadEvents 47 firstOptionalResolver *fakeResolver 48 lastOptionalResolver *fakeResolver 49 } 50 51 type resolverSuite struct { 52 baseResolverSuite 53 } 54 55 type caasResolverSuite struct { 56 resolverSuite 57 } 58 59 type iaasResolverSuite struct { 60 resolverSuite 61 } 62 63 type conflictedResolverSuite struct { 64 baseResolverSuite 65 } 66 67 type rebootResolverSuite struct { 68 baseResolverSuite 69 } 70 71 var _ = gc.Suite(&caasResolverSuite{}) 72 var _ = gc.Suite(&iaasResolverSuite{}) 73 var _ = gc.Suite(&conflictedResolverSuite{}) 74 var _ = gc.Suite(&rebootResolverSuite{}) 75 76 const rebootNotDetected = false 77 const rebootDetected = true 78 79 func (s *caasResolverSuite) SetUpTest(c *gc.C) { 80 s.resolverSuite.SetUpTest(c, model.CAAS, rebootNotDetected) 81 } 82 83 func (s *iaasResolverSuite) SetUpTest(c *gc.C) { 84 s.resolverSuite.SetUpTest(c, model.IAAS, rebootNotDetected) 85 } 86 87 func (s *conflictedResolverSuite) SetUpTest(_ *gc.C) { 88 // NoOp, required to not panic. 89 } 90 91 func (s *rebootResolverSuite) SetUpTest(_ *gc.C) { 92 // NoOp, required to not panic. 93 } 94 95 func (s *baseResolverSuite) SetUpTest(c *gc.C, modelType model.ModelType, rebootDetected bool) { 96 attachments, err := storage.NewAttachments(&dummyStorageAccessor{}, names.NewUnitTag("u/0"), &fakeRW{}, nil) 97 c.Assert(err, jc.ErrorIsNil) 98 secretsTracker, err := secrets.NewSecrets(&dummySecretsAccessor{}, names.NewUnitTag("u/0"), &fakeRW{}, nil) 99 c.Assert(err, jc.ErrorIsNil) 100 logger := loggo.GetLogger("test") 101 102 s.workloadEvents = container.NewWorkloadEvents() 103 s.firstOptionalResolver = &fakeResolver{} 104 s.lastOptionalResolver = &fakeResolver{} 105 s.resolverConfig = uniter.ResolverConfig{ 106 ClearResolved: func() error { return s.clearResolved() }, 107 ReportHookError: func(info hook.Info) error { return s.reportHookError(info) }, 108 StartRetryHookTimer: func() { s.stub.AddCall("StartRetryHookTimer") }, 109 StopRetryHookTimer: func() { s.stub.AddCall("StopRetryHookTimer") }, 110 ShouldRetryHooks: true, 111 UpgradeSeries: upgradeseries.NewResolver(logger), 112 Secrets: secrets.NewSecretsResolver(logger, secretsTracker, func(_ string) {}, func(_ string) {}, func(_ []string) {}), 113 Reboot: reboot.NewResolver(logger, rebootDetected), 114 Leadership: leadership.NewResolver(logger), 115 Actions: uniteractions.NewResolver(logger), 116 VerifyCharmProfile: verifycharmprofile.NewResolver(logger, modelType), 117 CreatedRelations: nopResolver{}, 118 Relations: nopResolver{}, 119 Storage: storage.NewResolver(logger, attachments, modelType), 120 Commands: nopResolver{}, 121 ModelType: modelType, 122 OptionalResolvers: []resolver.Resolver{ 123 s.firstOptionalResolver, 124 container.NewRemoteContainerInitResolver(), 125 container.NewWorkloadHookResolver(logger, s.workloadEvents, nil), 126 s.lastOptionalResolver, 127 }, 128 Logger: logger, 129 } 130 131 s.stub = testing.Stub{} 132 s.charmURL = "ch:precise/mysql-2" 133 s.remoteState = remotestate.Snapshot{ 134 CharmURL: s.charmURL, 135 } 136 s.opFactory = operation.NewFactory(operation.FactoryParams{ 137 Logger: loggo.GetLogger("test"), 138 }) 139 140 if s.clearResolved == nil { 141 s.clearResolved = func() error { 142 return errors.New("unexpected resolved") 143 } 144 } 145 146 s.reportHookError = func(hook.Info) error { 147 return nil 148 //return errors.New("unexpected report hook error") 149 } 150 151 s.resolver = uniter.NewUniterResolver(s.resolverConfig) 152 } 153 154 // TestStartedNotInstalled tests whether the Started flag overrides the 155 // Installed flag being unset, in the event of an unexpected inconsistency in 156 // local state. 157 func (s *resolverSuite) TestStartedNotInstalled(c *gc.C) { 158 localState := resolver.LocalState{ 159 CharmURL: s.charmURL, 160 State: operation.State{ 161 Kind: operation.Continue, 162 Installed: false, 163 Started: true, 164 }, 165 } 166 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 167 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 168 } 169 170 // TestNotStartedNotInstalled tests whether the next operation for an 171 // uninstalled local state is an install hook operation. 172 func (s *resolverSuite) TestNotStartedNotInstalled(c *gc.C) { 173 localState := resolver.LocalState{ 174 CharmURL: s.charmURL, 175 State: operation.State{ 176 Kind: operation.Continue, 177 Installed: false, 178 Started: false, 179 }, 180 } 181 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 182 c.Assert(err, jc.ErrorIsNil) 183 c.Assert(op.String(), gc.Equals, "run install hook") 184 } 185 186 func (s *iaasResolverSuite) TestUpgradeSeriesPrepareStatusChanged(c *gc.C) { 187 localState := resolver.LocalState{ 188 CharmURL: s.charmURL, 189 UpgradeMachineStatus: model.UpgradeSeriesNotStarted, 190 State: operation.State{ 191 Kind: operation.Continue, 192 Installed: true, 193 Started: true, 194 }, 195 } 196 197 s.remoteState.UpgradeMachineStatus = model.UpgradeSeriesPrepareStarted 198 s.remoteState.UpgradeMachineTarget = "ubuntu@20.04" 199 200 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 201 c.Assert(err, jc.ErrorIsNil) 202 c.Assert(op.String(), gc.Equals, "run pre-series-upgrade hook") 203 } 204 205 func (s *iaasResolverSuite) TestPostSeriesUpgradeHookRunsWhenConditionsAreMet(c *gc.C) { 206 localState := resolver.LocalState{ 207 CharmURL: s.charmURL, 208 UpgradeMachineStatus: model.UpgradeSeriesNotStarted, 209 LeaderSettingsVersion: 1, 210 State: operation.State{ 211 Kind: operation.Continue, 212 Installed: true, 213 Started: true, 214 ConfigHash: "version1", 215 }, 216 } 217 s.remoteState.UpgradeMachineStatus = model.UpgradeSeriesCompleteStarted 218 219 // Bumping the remote state versions verifies that the upgrade-series 220 // completion hook takes precedence. 221 s.remoteState.ConfigHash = "version2" 222 s.remoteState.LeaderSettingsVersion = 2 223 224 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 225 c.Assert(err, jc.ErrorIsNil) 226 c.Assert(op.String(), gc.Equals, "run post-series-upgrade hook") 227 } 228 229 func (s *iaasResolverSuite) TestRunsOperationToResetLocalUpgradeSeriesStateWhenConditionsAreMet(c *gc.C) { 230 localState := resolver.LocalState{ 231 CharmURL: s.charmURL, 232 UpgradeMachineStatus: model.UpgradeSeriesCompleted, 233 State: operation.State{ 234 Kind: operation.Continue, 235 Installed: true, 236 Started: true, 237 }, 238 } 239 s.remoteState.UpgradeMachineStatus = model.UpgradeSeriesNotStarted 240 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 241 c.Assert(err, jc.ErrorIsNil) 242 c.Assert(op.String(), gc.Equals, "complete upgrade series") 243 } 244 245 func (s *iaasResolverSuite) TestUniterIdlesWhenRemoteStateIsUpgradeSeriesCompleted(c *gc.C) { 246 localState := resolver.LocalState{ 247 UpgradeMachineStatus: model.UpgradeSeriesNotStarted, 248 CharmURL: s.charmURL, 249 State: operation.State{ 250 Kind: operation.Continue, 251 Installed: true, 252 }, 253 } 254 s.remoteState.UpgradeMachineStatus = model.UpgradeSeriesPrepareCompleted 255 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 256 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 257 } 258 259 func (s *resolverSuite) TestQueuedHookOnAgentRestart(c *gc.C) { 260 s.resolver = uniter.NewUniterResolver(s.resolverConfig) 261 s.reportHookError = func(hook.Info) error { return errors.New("unexpected") } 262 queued := operation.Queued 263 localState := resolver.LocalState{ 264 CharmURL: s.charmURL, 265 State: operation.State{ 266 Kind: operation.RunHook, 267 Step: operation.Pending, 268 Installed: true, 269 Started: true, 270 Hook: &hook.Info{ 271 Kind: hooks.ConfigChanged, 272 }, 273 HookStep: &queued, 274 }, 275 } 276 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 277 c.Assert(err, jc.ErrorIsNil) 278 c.Assert(op.String(), gc.Equals, "run config-changed hook") 279 s.stub.CheckNoCalls(c) 280 } 281 282 func (s *resolverSuite) TestPendingHookOnAgentRestart(c *gc.C) { 283 s.resolverConfig.ShouldRetryHooks = false 284 s.resolver = uniter.NewUniterResolver(s.resolverConfig) 285 hookError := false 286 s.reportHookError = func(hook.Info) error { 287 hookError = true 288 return nil 289 } 290 queued := operation.Pending 291 localState := resolver.LocalState{ 292 CharmURL: s.charmURL, 293 State: operation.State{ 294 Kind: operation.RunHook, 295 Step: operation.Pending, 296 Installed: true, 297 Started: true, 298 Hook: &hook.Info{ 299 Kind: hooks.ConfigChanged, 300 }, 301 HookStep: &queued, 302 }, 303 } 304 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 305 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 306 c.Assert(hookError, jc.IsTrue) 307 s.stub.CheckNoCalls(c) 308 } 309 310 func (s *resolverSuite) TestHookErrorDoesNotStartRetryTimerIfShouldRetryFalse(c *gc.C) { 311 s.resolverConfig.ShouldRetryHooks = false 312 s.resolver = uniter.NewUniterResolver(s.resolverConfig) 313 s.reportHookError = func(hook.Info) error { return nil } 314 localState := resolver.LocalState{ 315 CharmURL: s.charmURL, 316 State: operation.State{ 317 Kind: operation.RunHook, 318 Step: operation.Pending, 319 Installed: true, 320 Started: true, 321 Hook: &hook.Info{ 322 Kind: hooks.ConfigChanged, 323 }, 324 }, 325 } 326 // Run the resolver; we should not attempt a hook retry 327 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 328 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 329 s.stub.CheckNoCalls(c) 330 } 331 332 func (s *resolverSuite) TestHookErrorStartRetryTimer(c *gc.C) { 333 s.reportHookError = func(hook.Info) error { return nil } 334 localState := resolver.LocalState{ 335 CharmURL: s.charmURL, 336 State: operation.State{ 337 Kind: operation.RunHook, 338 Step: operation.Pending, 339 Installed: true, 340 Started: true, 341 Hook: &hook.Info{ 342 Kind: hooks.ConfigChanged, 343 }, 344 }, 345 } 346 // Run the resolver twice; we should start the hook retry 347 // timer on the first time through, no change on the second. 348 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 349 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 350 s.stub.CheckCallNames(c, "StartRetryHookTimer") 351 352 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 353 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 354 s.stub.CheckCallNames(c, "StartRetryHookTimer") // no change 355 } 356 357 func (s *resolverSuite) TestHookErrorStartRetryTimerAgain(c *gc.C) { 358 s.reportHookError = func(hook.Info) error { return nil } 359 localState := resolver.LocalState{ 360 CharmURL: s.charmURL, 361 State: operation.State{ 362 Kind: operation.RunHook, 363 Step: operation.Pending, 364 Installed: true, 365 Started: true, 366 Hook: &hook.Info{ 367 Kind: hooks.ConfigChanged, 368 }, 369 }, 370 } 371 372 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 373 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 374 s.stub.CheckCallNames(c, "StartRetryHookTimer") 375 376 s.remoteState.RetryHookVersion = 1 377 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 378 c.Assert(err, jc.ErrorIsNil) 379 c.Assert(op.String(), gc.Equals, "run config-changed hook") 380 s.stub.CheckCallNames(c, "StartRetryHookTimer") // no change 381 localState.RetryHookVersion = 1 382 383 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 384 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 385 s.stub.CheckCallNames(c, "StartRetryHookTimer", "StartRetryHookTimer") 386 } 387 388 func (s *resolverSuite) TestResolvedRetryHooksStopRetryTimer(c *gc.C) { 389 // Resolving a failed hook should stop the retry timer. 390 s.testResolveHookErrorStopRetryTimer(c, params.ResolvedRetryHooks) 391 } 392 393 func (s *resolverSuite) TestResolvedNoHooksStopRetryTimer(c *gc.C) { 394 // Resolving a failed hook should stop the retry timer. 395 s.testResolveHookErrorStopRetryTimer(c, params.ResolvedNoHooks) 396 } 397 398 func (s *resolverSuite) testResolveHookErrorStopRetryTimer(c *gc.C, mode params.ResolvedMode) { 399 s.stub.ResetCalls() 400 s.clearResolved = func() error { return nil } 401 s.reportHookError = func(hook.Info) error { return nil } 402 localState := resolver.LocalState{ 403 CharmURL: s.charmURL, 404 State: operation.State{ 405 Kind: operation.RunHook, 406 Step: operation.Pending, 407 Installed: true, 408 Started: true, 409 Hook: &hook.Info{ 410 Kind: hooks.ConfigChanged, 411 }, 412 }, 413 } 414 415 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 416 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 417 s.stub.CheckCallNames(c, "StartRetryHookTimer") 418 419 s.remoteState.ResolvedMode = mode 420 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 421 c.Assert(err, jc.ErrorIsNil) 422 s.stub.CheckCallNames(c, "StartRetryHookTimer", "StopRetryHookTimer") 423 } 424 425 func (s *resolverSuite) TestRunHookStopRetryTimer(c *gc.C) { 426 s.reportHookError = func(hook.Info) error { return nil } 427 localState := resolver.LocalState{ 428 CharmURL: s.charmURL, 429 State: operation.State{ 430 Kind: operation.RunHook, 431 Step: operation.Pending, 432 Installed: true, 433 Started: true, 434 Hook: &hook.Info{ 435 Kind: hooks.ConfigChanged, 436 }, 437 }, 438 } 439 440 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 441 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 442 s.stub.CheckCallNames(c, "StartRetryHookTimer") 443 444 localState.Kind = operation.Continue 445 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 446 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 447 s.stub.CheckCallNames(c, "StartRetryHookTimer", "StopRetryHookTimer") 448 } 449 450 func (s *resolverSuite) TestRunsConfigChangedIfConfigHashChanges(c *gc.C) { 451 localState := resolver.LocalState{ 452 CharmURL: s.charmURL, 453 State: operation.State{ 454 Kind: operation.Continue, 455 Installed: true, 456 Started: true, 457 ConfigHash: "somehash", 458 }, 459 } 460 s.remoteState.ConfigHash = "differenthash" 461 462 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 463 c.Assert(err, jc.ErrorIsNil) 464 c.Assert(op.String(), gc.Equals, "run config-changed hook") 465 } 466 467 func (s *resolverSuite) TestRunsConfigChangedIfTrustHashChanges(c *gc.C) { 468 localState := resolver.LocalState{ 469 CharmURL: s.charmURL, 470 State: operation.State{ 471 Kind: operation.Continue, 472 Installed: true, 473 Started: true, 474 TrustHash: "somehash", 475 }, 476 } 477 s.remoteState.TrustHash = "differenthash" 478 479 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 480 c.Assert(err, jc.ErrorIsNil) 481 c.Assert(op.String(), gc.Equals, "run config-changed hook") 482 } 483 484 func (s *resolverSuite) TestRunsConfigChangedIfAddressesHashChanges(c *gc.C) { 485 localState := resolver.LocalState{ 486 CharmURL: s.charmURL, 487 State: operation.State{ 488 Kind: operation.Continue, 489 Installed: true, 490 Started: true, 491 AddressesHash: "somehash", 492 }, 493 } 494 s.remoteState.AddressesHash = "differenthash" 495 496 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 497 c.Assert(err, jc.ErrorIsNil) 498 c.Assert(op.String(), gc.Equals, "run config-changed hook") 499 } 500 501 func (s *resolverSuite) TestNoOperationIfHashesAllMatch(c *gc.C) { 502 localState := resolver.LocalState{ 503 CharmURL: s.charmURL, 504 State: operation.State{ 505 Kind: operation.Continue, 506 Installed: true, 507 Started: true, 508 ConfigHash: "config", 509 TrustHash: "trust", 510 AddressesHash: "addresses", 511 }, 512 } 513 s.remoteState.ConfigHash = "config" 514 s.remoteState.TrustHash = "trust" 515 s.remoteState.AddressesHash = "addresses" 516 517 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 518 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 519 } 520 521 func (s *resolverSuite) TestUpgradeOperation(c *gc.C) { 522 opFactory := setupUpgradeOpFactory() 523 localState := resolver.LocalState{ 524 CharmURL: s.charmURL, 525 State: operation.State{ 526 Kind: operation.Upgrade, 527 Installed: true, 528 Started: true, 529 }, 530 } 531 op, err := s.resolver.NextOp(localState, s.remoteState, opFactory) 532 c.Assert(err, jc.ErrorIsNil) 533 c.Assert(op.String(), gc.Equals, fmt.Sprintf("upgrade to %s", s.charmURL)) 534 } 535 536 func (s *iaasResolverSuite) TestUpgradeOperationVerifyCPFail(c *gc.C) { 537 opFactory := setupUpgradeOpFactory() 538 localState := resolver.LocalState{ 539 CharmURL: s.charmURL, 540 State: operation.State{ 541 Kind: operation.Upgrade, 542 Installed: true, 543 Started: true, 544 }, 545 } 546 s.remoteState.CharmProfileRequired = true 547 _, err := s.resolver.NextOp(localState, s.remoteState, opFactory) 548 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 549 } 550 551 func (s *resolverSuite) TestContinueUpgradeOperation(c *gc.C) { 552 opFactory := setupUpgradeOpFactory() 553 localState := resolver.LocalState{ 554 CharmURL: s.charmURL, 555 State: operation.State{ 556 Kind: operation.Continue, 557 Installed: true, 558 Started: true, 559 }, 560 } 561 s.setupForceCharmModifiedTrue() 562 op, err := s.resolver.NextOp(localState, s.remoteState, opFactory) 563 c.Assert(err, jc.ErrorIsNil) 564 c.Assert(op.String(), gc.Equals, fmt.Sprintf("upgrade to %s", s.charmURL)) 565 } 566 567 func (s *resolverSuite) TestNoOperationWithOptionalResolvers(c *gc.C) { 568 localState := resolver.LocalState{ 569 CharmURL: s.charmURL, 570 State: operation.State{ 571 Kind: operation.Continue, 572 Installed: true, 573 Started: true, 574 ConfigHash: "config", 575 TrustHash: "trust", 576 AddressesHash: "addresses", 577 }, 578 } 579 s.remoteState.ConfigHash = "config" 580 s.remoteState.TrustHash = "trust" 581 s.remoteState.AddressesHash = "addresses" 582 583 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 584 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 585 c.Assert(s.firstOptionalResolver.callCount, gc.Equals, 1) 586 c.Assert(s.lastOptionalResolver.callCount, gc.Equals, 1) 587 } 588 589 func (s *resolverSuite) TestOperationWithOptionalResolvers(c *gc.C) { 590 localState := resolver.LocalState{ 591 CharmURL: s.charmURL, 592 State: operation.State{ 593 Kind: operation.Continue, 594 Installed: true, 595 Started: true, 596 ConfigHash: "config", 597 TrustHash: "trust", 598 AddressesHash: "addresses", 599 }, 600 } 601 s.remoteState.ConfigHash = "config" 602 s.remoteState.TrustHash = "trust" 603 s.remoteState.AddressesHash = "addresses" 604 605 s.firstOptionalResolver.op = &fakeNoOp{} 606 607 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 608 c.Assert(err, jc.ErrorIsNil) 609 c.Assert(op, gc.Equals, s.firstOptionalResolver.op) 610 c.Assert(s.firstOptionalResolver.callCount, gc.Equals, 1) 611 c.Assert(s.lastOptionalResolver.callCount, gc.Equals, 0) 612 } 613 614 func (s *iaasResolverSuite) TestContinueUpgradeOperationVerifyCPFail(c *gc.C) { 615 opFactory := setupUpgradeOpFactory() 616 localState := resolver.LocalState{ 617 CharmURL: s.charmURL, 618 State: operation.State{ 619 Kind: operation.Continue, 620 Installed: true, 621 Started: true, 622 }, 623 } 624 s.setupForceCharmModifiedTrue() 625 s.remoteState.CharmProfileRequired = true 626 _, err := s.resolver.NextOp(localState, s.remoteState, opFactory) 627 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 628 } 629 630 func (s *resolverSuite) TestRunHookPendingUpgradeOperation(c *gc.C) { 631 opFactory := setupUpgradeOpFactory() 632 localState := resolver.LocalState{ 633 CharmURL: s.charmURL, 634 State: operation.State{ 635 Kind: operation.RunHook, 636 Hook: &hook.Info{Kind: hooks.ConfigChanged}, 637 Installed: true, 638 Started: true, 639 Step: operation.Pending, 640 }, 641 } 642 s.setupForceCharmModifiedTrue() 643 op, err := s.resolver.NextOp(localState, s.remoteState, opFactory) 644 c.Assert(err, jc.ErrorIsNil) 645 c.Assert(op.String(), gc.Equals, fmt.Sprintf("upgrade to %s", s.charmURL)) 646 } 647 648 func (s *resolverSuite) TestRunsSecretRotated(c *gc.C) { 649 localState := resolver.LocalState{ 650 State: operation.State{ 651 Kind: operation.Continue, 652 Installed: true, 653 Started: true, 654 Leader: true, 655 }, 656 } 657 s.remoteState.Leader = true 658 s.remoteState.SecretRotations = []string{"secret:9m4e2mr0ui3e8a215n4g"} 659 660 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 661 c.Assert(err, jc.ErrorIsNil) 662 c.Assert(op.String(), gc.Equals, "run secret-rotate (secret:9m4e2mr0ui3e8a215n4g) hook") 663 } 664 665 func (s *conflictedResolverSuite) TestNextOpConflicted(c *gc.C) { 666 s.baseResolverSuite.SetUpTest(c, model.IAAS, rebootNotDetected) 667 opFactory := setupUpgradeOpFactory() 668 localState := resolver.LocalState{ 669 CharmURL: s.charmURL, 670 Conflicted: true, 671 State: operation.State{ 672 Kind: operation.Upgrade, 673 Installed: true, 674 Started: true, 675 }, 676 } 677 _, err := s.resolver.NextOp(localState, s.remoteState, opFactory) 678 c.Assert(err, gc.Equals, resolver.ErrWaiting) 679 } 680 681 func (s *conflictedResolverSuite) TestNextOpConflictedVerifyCPFail(c *gc.C) { 682 s.baseResolverSuite.SetUpTest(c, model.IAAS, rebootNotDetected) 683 opFactory := setupUpgradeOpFactory() 684 localState := resolver.LocalState{ 685 CharmURL: s.charmURL, 686 Conflicted: true, 687 State: operation.State{ 688 Kind: operation.Upgrade, 689 Installed: true, 690 Started: true, 691 }, 692 } 693 s.remoteState.CharmProfileRequired = true 694 _, err := s.resolver.NextOp(localState, s.remoteState, opFactory) 695 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 696 } 697 698 func (s *conflictedResolverSuite) TestNextOpConflictedNewResolvedUpgrade(c *gc.C) { 699 s.clearResolved = func() error { 700 return nil 701 } 702 s.baseResolverSuite.SetUpTest(c, model.IAAS, rebootNotDetected) 703 opFactory := setupUpgradeOpFactory() 704 localState := resolver.LocalState{ 705 CharmURL: s.charmURL, 706 Conflicted: true, 707 State: operation.State{ 708 Kind: operation.Upgrade, 709 Installed: true, 710 Started: true, 711 }, 712 } 713 s.remoteState.ResolvedMode = params.ResolvedRetryHooks 714 op, err := s.resolver.NextOp(localState, s.remoteState, opFactory) 715 c.Assert(err, jc.ErrorIsNil) 716 c.Assert(op.String(), gc.Equals, fmt.Sprintf("continue upgrade to %s", s.charmURL)) 717 } 718 719 func (s *conflictedResolverSuite) TestNextOpConflictedNewRevertUpgrade(c *gc.C) { 720 s.baseResolverSuite.SetUpTest(c, model.IAAS, rebootNotDetected) 721 opFactory := setupUpgradeOpFactory() 722 localState := resolver.LocalState{ 723 CharmURL: s.charmURL, 724 Conflicted: true, 725 State: operation.State{ 726 Kind: operation.Upgrade, 727 Installed: true, 728 Started: true, 729 }, 730 } 731 s.setupForceCharmModifiedTrue() 732 op, err := s.resolver.NextOp(localState, s.remoteState, opFactory) 733 c.Assert(err, jc.ErrorIsNil) 734 c.Assert(op.String(), gc.Equals, fmt.Sprintf("switch upgrade to %s", s.charmURL)) 735 } 736 737 func (s *rebootResolverSuite) TestNopResolverForNonIAASModels(c *gc.C) { 738 s.baseResolverSuite.SetUpTest(c, model.CAAS, rebootDetected) 739 740 localState := resolver.LocalState{ 741 CharmURL: s.charmURL, 742 State: operation.State{ 743 Kind: operation.Continue, 744 Installed: true, 745 Started: true, 746 }, 747 } 748 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 749 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 750 } 751 752 func (s *rebootResolverSuite) TestStartHookTriggerPostReboot(c *gc.C) { 753 s.baseResolverSuite.SetUpTest(c, model.IAAS, rebootDetected) 754 755 localState := resolver.LocalState{ 756 CharmURL: s.charmURL, 757 State: operation.State{ 758 Kind: operation.Continue, 759 Installed: true, 760 Started: true, // charm must be started 761 }, 762 } 763 s.remoteState.UpgradeMachineStatus = model.UpgradeSeriesNotStarted 764 765 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 766 c.Assert(err, jc.ErrorIsNil) 767 c.Assert(op.String(), gc.Equals, "run start hook") 768 769 // Ensure that start-post-reboot is only triggered once 770 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 771 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 772 } 773 774 func (s *rebootResolverSuite) TestStartHookDeferredWhenUpgradeIsInProgress(c *gc.C) { 775 s.baseResolverSuite.SetUpTest(c, model.IAAS, rebootDetected) 776 777 localState := resolver.LocalState{ 778 CharmURL: s.charmURL, 779 UpgradeMachineStatus: model.UpgradeSeriesNotStarted, 780 State: operation.State{ 781 Kind: operation.Continue, 782 Installed: true, 783 Started: true, // charm must be started 784 }, 785 } 786 787 // Controller indicates a series upgrade is in progress 788 statusChecks := []struct { 789 status model.UpgradeSeriesStatus 790 expOp string 791 expErr error 792 }{ 793 { 794 status: model.UpgradeSeriesPrepareStarted, 795 expOp: "run pre-series-upgrade hook", 796 }, 797 { 798 status: model.UpgradeSeriesPrepareRunning, 799 expErr: resolver.ErrNoOperation, 800 }, 801 { 802 status: model.UpgradeSeriesPrepareCompleted, 803 expErr: resolver.ErrNoOperation, 804 }, 805 { 806 status: model.UpgradeSeriesCompleteStarted, 807 expOp: "run post-series-upgrade hook", 808 }, 809 { 810 status: model.UpgradeSeriesCompleteRunning, 811 expErr: resolver.ErrNoOperation, 812 }, 813 { 814 status: model.UpgradeSeriesCompleted, 815 expErr: resolver.ErrNoOperation, 816 }, 817 } 818 819 for _, statusTest := range statusChecks { 820 c.Logf("triggering resolver with upgrade status: %s", statusTest.status) 821 s.remoteState.UpgradeMachineStatus = statusTest.status 822 s.remoteState.UpgradeMachineTarget = "ubuntu@20.04" 823 824 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 825 if statusTest.expErr != nil { 826 c.Assert(err, gc.Equals, statusTest.expErr) 827 } else { 828 c.Assert(err, jc.ErrorIsNil) 829 c.Assert(op.String(), gc.Equals, statusTest.expOp) 830 } 831 } 832 833 // Mutate remote state to indicate that upgrade has not been started 834 // and run the resolver again. This time, we should get back the 835 // deferred start hook. 836 s.remoteState.UpgradeMachineStatus = model.UpgradeSeriesNotStarted 837 838 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 839 c.Assert(err, jc.ErrorIsNil) 840 c.Assert(op.String(), gc.Equals, "run start hook") 841 842 // Ensure that start-post-reboot is only triggered once 843 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 844 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 845 } 846 847 func setupUpgradeOpFactory() operation.Factory { 848 return operation.NewFactory(operation.FactoryParams{ 849 Deployer: &fakeDeployer{}, 850 Logger: loggo.GetLogger("test"), 851 }) 852 } 853 854 func (s *baseResolverSuite) setupForceCharmModifiedTrue() { 855 s.remoteState.ForceCharmUpgrade = true 856 s.remoteState.CharmModifiedVersion = 3 857 } 858 859 // fakeRW implements the storage.UnitStateReadWriter interface 860 // so SetUpTests can call storage.NewAttachments. It doesn't 861 // need to do anything. 862 type fakeRW struct { 863 } 864 865 func (m *fakeRW) State() (params.UnitStateResult, error) { 866 return params.UnitStateResult{}, nil 867 } 868 869 func (m *fakeRW) SetState(_ params.SetUnitStateArg) error { 870 return nil 871 } 872 873 // fakeDeployer implements the charm.Deployter interface 874 // so Upgrade operations can call validate deployer not nil. It doesn't 875 // need to do anything. 876 type fakeDeployer struct { 877 } 878 879 func (m *fakeDeployer) Stage(_ unitercharm.BundleInfo, _ <-chan struct{}) error { 880 return nil 881 } 882 883 func (m *fakeDeployer) Deploy() error { 884 return nil 885 } 886 887 type fakeResolver struct { 888 callCount int 889 op operation.Operation 890 } 891 892 func (r *fakeResolver) NextOp( 893 localState resolver.LocalState, 894 remoteState remotestate.Snapshot, 895 opFactory operation.Factory, 896 ) (operation.Operation, error) { 897 r.callCount++ 898 if r.op != nil { 899 return r.op, nil 900 } 901 return nil, resolver.ErrNoOperation 902 } 903 904 type fakeNoOp struct { 905 operation.Operation 906 }