github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "github.com/juju/errors" 8 "github.com/juju/testing" 9 jc "github.com/juju/testing/checkers" 10 gc "gopkg.in/check.v1" 11 "gopkg.in/juju/charm.v6" 12 "gopkg.in/juju/charm.v6/hooks" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/core/lxdprofile" 17 "github.com/juju/juju/core/model" 18 "github.com/juju/juju/worker/uniter" 19 uniteractions "github.com/juju/juju/worker/uniter/actions" 20 "github.com/juju/juju/worker/uniter/hook" 21 "github.com/juju/juju/worker/uniter/leadership" 22 "github.com/juju/juju/worker/uniter/operation" 23 "github.com/juju/juju/worker/uniter/relation" 24 "github.com/juju/juju/worker/uniter/remotestate" 25 "github.com/juju/juju/worker/uniter/resolver" 26 "github.com/juju/juju/worker/uniter/storage" 27 "github.com/juju/juju/worker/uniter/upgradecharmprofile" 28 "github.com/juju/juju/worker/uniter/upgradeseries" 29 ) 30 31 type resolverSuite struct { 32 stub testing.Stub 33 charmModifiedVersion int 34 charmURL *charm.URL 35 remoteState remotestate.Snapshot 36 opFactory operation.Factory 37 resolver resolver.Resolver 38 resolverConfig uniter.ResolverConfig 39 modelType model.ModelType 40 41 clearResolved func() error 42 reportHookError func(hook.Info) error 43 } 44 45 type caasResolverSuite struct { 46 resolverSuite 47 } 48 49 type iaasResolverSuite struct { 50 resolverSuite 51 } 52 53 var _ = gc.Suite(&caasResolverSuite{}) 54 var _ = gc.Suite(&iaasResolverSuite{}) 55 56 func (s *caasResolverSuite) SetUpTest(c *gc.C) { 57 s.modelType = model.CAAS 58 s.resolverSuite.SetUpTest(c) 59 } 60 61 func (s *iaasResolverSuite) SetUpTest(c *gc.C) { 62 s.modelType = model.IAAS 63 s.resolverSuite.SetUpTest(c) 64 s.resolver = uniter.NewUniterResolver(s.resolverConfig) 65 } 66 67 func (s *resolverSuite) SetUpTest(c *gc.C) { 68 s.stub = testing.Stub{} 69 s.charmURL = charm.MustParseURL("cs:precise/mysql-2") 70 s.remoteState = remotestate.Snapshot{ 71 CharmModifiedVersion: s.charmModifiedVersion, 72 CharmURL: s.charmURL, 73 } 74 s.opFactory = operation.NewFactory(operation.FactoryParams{}) 75 76 attachments, err := storage.NewAttachments(&dummyStorageAccessor{}, names.NewUnitTag("u/0"), c.MkDir(), nil) 77 c.Assert(err, jc.ErrorIsNil) 78 79 s.clearResolved = func() error { 80 return errors.New("unexpected resolved") 81 } 82 83 s.reportHookError = func(hook.Info) error { 84 return errors.New("unexpected report hook error") 85 } 86 87 s.resolverConfig = uniter.ResolverConfig{ 88 ClearResolved: func() error { return s.clearResolved() }, 89 ReportHookError: func(info hook.Info) error { return s.reportHookError(info) }, 90 StartRetryHookTimer: func() { s.stub.AddCall("StartRetryHookTimer") }, 91 StopRetryHookTimer: func() { s.stub.AddCall("StopRetryHookTimer") }, 92 ShouldRetryHooks: true, 93 UpgradeSeries: upgradeseries.NewResolver(), 94 UpgradeCharmProfile: upgradecharmprofile.NewResolver(), 95 Leadership: leadership.NewResolver(), 96 Actions: uniteractions.NewResolver(), 97 Relations: relation.NewRelationsResolver(&dummyRelations{}), 98 Storage: storage.NewResolver(attachments, s.modelType), 99 Commands: nopResolver{}, 100 ModelType: s.modelType, 101 } 102 103 s.resolver = uniter.NewUniterResolver(s.resolverConfig) 104 } 105 106 // TestStartedNotInstalled tests whether the Started flag overrides the 107 // Installed flag being unset, in the event of an unexpected inconsistency in 108 // local state. 109 func (s *resolverSuite) TestStartedNotInstalled(c *gc.C) { 110 localState := resolver.LocalState{ 111 CharmModifiedVersion: s.charmModifiedVersion, 112 CharmURL: s.charmURL, 113 State: operation.State{ 114 Kind: operation.Continue, 115 Installed: false, 116 Started: true, 117 }, 118 } 119 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 120 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 121 } 122 123 // TestNotStartedNotInstalled tests whether the next operation for an 124 // uninstalled local state is an install hook operation. 125 func (s *resolverSuite) TestNotStartedNotInstalled(c *gc.C) { 126 localState := resolver.LocalState{ 127 CharmModifiedVersion: s.charmModifiedVersion, 128 CharmURL: s.charmURL, 129 State: operation.State{ 130 Kind: operation.Continue, 131 Installed: false, 132 Started: false, 133 }, 134 } 135 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 136 c.Assert(err, jc.ErrorIsNil) 137 c.Assert(op.String(), gc.Equals, "run install hook") 138 } 139 140 func (s *iaasResolverSuite) TestCharmModifiedTakesPrecedenceOverRelationsChanges(c *gc.C) { 141 localState := resolver.LocalState{ 142 CharmModifiedVersion: s.charmModifiedVersion, 143 CharmURL: s.charmURL, 144 State: operation.State{ 145 Kind: operation.Continue, 146 Installed: true, 147 Started: true, 148 }, 149 } 150 s.remoteState.CharmModifiedVersion = s.charmModifiedVersion + 1 151 s.remoteState.UpgradeCharmProfileStatus = lxdprofile.NotRequiredStatus 152 // Change relation state (to simulate simultaneous change remote state update) 153 s.remoteState.Relations = map[int]remotestate.RelationSnapshot{0: {}} 154 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 155 c.Assert(err, jc.ErrorIsNil) 156 c.Assert(op.String(), gc.Equals, "upgrade to cs:precise/mysql-2") 157 } 158 159 func (s *iaasResolverSuite) TestUpgradeSeriesPrepareStatusChanged(c *gc.C) { 160 localState := resolver.LocalState{ 161 CharmModifiedVersion: s.charmModifiedVersion, 162 CharmURL: s.charmURL, 163 UpgradeSeriesStatus: model.UpgradeSeriesNotStarted, 164 State: operation.State{ 165 Kind: operation.Continue, 166 Installed: true, 167 Started: true, 168 }, 169 } 170 s.remoteState.UpgradeSeriesStatus = model.UpgradeSeriesPrepareStarted 171 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 172 c.Assert(err, jc.ErrorIsNil) 173 c.Assert(op.String(), gc.Equals, "run pre-series-upgrade hook") 174 } 175 176 func (s *iaasResolverSuite) TestPostSeriesUpgradeHookRunsWhenConditionsAreMet(c *gc.C) { 177 localState := resolver.LocalState{ 178 CharmModifiedVersion: s.charmModifiedVersion, 179 CharmURL: s.charmURL, 180 UpgradeSeriesStatus: model.UpgradeSeriesNotStarted, 181 LeaderSettingsVersion: 1, 182 State: operation.State{ 183 Kind: operation.Continue, 184 Installed: true, 185 Started: true, 186 ConfigHash: "version1", 187 }, 188 } 189 s.remoteState.UpgradeSeriesStatus = model.UpgradeSeriesCompleteStarted 190 191 // Bumping the remote state versions verifies that the upgrade-series 192 // completion hook takes precedence. 193 s.remoteState.ConfigHash = "version2" 194 s.remoteState.LeaderSettingsVersion = 2 195 196 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 197 c.Assert(err, jc.ErrorIsNil) 198 c.Assert(op.String(), gc.Equals, "run post-series-upgrade hook") 199 } 200 201 func (s *iaasResolverSuite) TestRunsOperationToResetLocalUpgradeSeriesStateWhenConditionsAreMet(c *gc.C) { 202 localState := resolver.LocalState{ 203 CharmModifiedVersion: s.charmModifiedVersion, 204 CharmURL: s.charmURL, 205 UpgradeSeriesStatus: model.UpgradeSeriesCompleted, 206 State: operation.State{ 207 Kind: operation.Continue, 208 Installed: true, 209 Started: true, 210 }, 211 } 212 s.remoteState.UpgradeSeriesStatus = model.UpgradeSeriesNotStarted 213 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 214 c.Assert(err, jc.ErrorIsNil) 215 c.Assert(op.String(), gc.Equals, "complete upgrade series") 216 } 217 218 func (s *iaasResolverSuite) TestUniterIdlesWhenRemoteStateIsUpgradeSeriesCompleted(c *gc.C) { 219 localState := resolver.LocalState{ 220 UpgradeSeriesStatus: model.UpgradeSeriesNotStarted, 221 CharmURL: s.charmURL, 222 State: operation.State{ 223 Kind: operation.Continue, 224 Installed: true, 225 }, 226 } 227 s.remoteState.UpgradeSeriesStatus = model.UpgradeSeriesPrepareCompleted 228 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 229 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 230 } 231 232 func (s *iaasResolverSuite) TestUpgradeCharmProfileWhenNotRequired(c *gc.C) { 233 localState := resolver.LocalState{ 234 CharmModifiedVersion: s.charmModifiedVersion, 235 CharmURL: s.charmURL, 236 UpgradeCharmProfileStatus: lxdprofile.NotRequiredStatus, 237 State: operation.State{ 238 Kind: operation.Upgrade, 239 Installed: true, 240 Started: true, 241 }, 242 } 243 s.remoteState.UpgradeCharmProfileStatus = lxdprofile.NotRequiredStatus 244 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 245 c.Assert(err, jc.ErrorIsNil) 246 c.Assert(op.String(), gc.Equals, "upgrade to cs:precise/mysql-2") 247 } 248 249 func (s *iaasResolverSuite) TestUpgradeCharmProfileWhenSuccess(c *gc.C) { 250 localState := resolver.LocalState{ 251 CharmModifiedVersion: s.charmModifiedVersion, 252 CharmURL: s.charmURL, 253 UpgradeCharmProfileStatus: lxdprofile.SuccessStatus, 254 State: operation.State{ 255 Kind: operation.Upgrade, 256 Installed: true, 257 Started: true, 258 }, 259 } 260 s.remoteState.UpgradeCharmProfileStatus = lxdprofile.SuccessStatus 261 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 262 c.Assert(err, jc.ErrorIsNil) 263 c.Assert(op.String(), gc.Equals, "upgrade to cs:precise/mysql-2") 264 } 265 266 func (s *iaasResolverSuite) TestUpgradeCharmProfileWhenErrorState(c *gc.C) { 267 localState := resolver.LocalState{ 268 CharmModifiedVersion: s.charmModifiedVersion, 269 CharmURL: s.charmURL, 270 UpgradeCharmProfileStatus: lxdprofile.ErrorStatus, 271 State: operation.State{ 272 Kind: operation.Upgrade, 273 Installed: true, 274 Started: true, 275 }, 276 } 277 s.remoteState.UpgradeCharmProfileStatus = lxdprofile.ErrorStatus 278 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 279 c.Assert(err, jc.ErrorIsNil) 280 c.Assert(op.String(), gc.Equals, "finish upgrade charm profile") 281 } 282 283 func (s *iaasResolverSuite) TestUpgradeCharmProfileWhenLocalStateIsNotErroredState(c *gc.C) { 284 localState := resolver.LocalState{ 285 CharmModifiedVersion: s.charmModifiedVersion, 286 CharmURL: s.charmURL, 287 UpgradeCharmProfileStatus: lxdprofile.NotRequiredStatus, 288 State: operation.State{ 289 Kind: operation.Upgrade, 290 Installed: true, 291 Started: true, 292 }, 293 } 294 s.remoteState.UpgradeCharmProfileStatus = lxdprofile.ErrorStatus 295 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 296 c.Assert(err, jc.ErrorIsNil) 297 c.Assert(op.String(), gc.Equals, "finish upgrade charm profile") 298 } 299 300 func (s *resolverSuite) TestHookErrorDoesNotStartRetryTimerIfShouldRetryFalse(c *gc.C) { 301 s.resolverConfig.ShouldRetryHooks = false 302 s.resolver = uniter.NewUniterResolver(s.resolverConfig) 303 s.reportHookError = func(hook.Info) error { return nil } 304 localState := resolver.LocalState{ 305 CharmURL: s.charmURL, 306 State: operation.State{ 307 Kind: operation.RunHook, 308 Step: operation.Pending, 309 Installed: true, 310 Started: true, 311 Hook: &hook.Info{ 312 Kind: hooks.ConfigChanged, 313 }, 314 }, 315 } 316 // Run the resolver; we should not attempt a hook retry 317 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 318 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 319 s.stub.CheckNoCalls(c) 320 } 321 322 func (s *resolverSuite) TestHookErrorStartRetryTimer(c *gc.C) { 323 s.reportHookError = func(hook.Info) error { return nil } 324 localState := resolver.LocalState{ 325 CharmModifiedVersion: s.charmModifiedVersion, 326 CharmURL: s.charmURL, 327 State: operation.State{ 328 Kind: operation.RunHook, 329 Step: operation.Pending, 330 Installed: true, 331 Started: true, 332 Hook: &hook.Info{ 333 Kind: hooks.ConfigChanged, 334 }, 335 }, 336 } 337 // Run the resolver twice; we should start the hook retry 338 // timer on the first time through, no change on the second. 339 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 340 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 341 s.stub.CheckCallNames(c, "StartRetryHookTimer") 342 343 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 344 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 345 s.stub.CheckCallNames(c, "StartRetryHookTimer") // no change 346 } 347 348 func (s *resolverSuite) TestHookErrorStartRetryTimerAgain(c *gc.C) { 349 s.reportHookError = func(hook.Info) error { return nil } 350 localState := resolver.LocalState{ 351 CharmModifiedVersion: s.charmModifiedVersion, 352 CharmURL: s.charmURL, 353 State: operation.State{ 354 Kind: operation.RunHook, 355 Step: operation.Pending, 356 Installed: true, 357 Started: true, 358 Hook: &hook.Info{ 359 Kind: hooks.ConfigChanged, 360 }, 361 }, 362 } 363 364 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 365 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 366 s.stub.CheckCallNames(c, "StartRetryHookTimer") 367 368 s.remoteState.RetryHookVersion = 1 369 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 370 c.Assert(err, jc.ErrorIsNil) 371 c.Assert(op.String(), gc.Equals, "run config-changed hook") 372 s.stub.CheckCallNames(c, "StartRetryHookTimer") // no change 373 localState.RetryHookVersion = 1 374 375 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 376 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 377 s.stub.CheckCallNames(c, "StartRetryHookTimer", "StartRetryHookTimer") 378 } 379 380 func (s *resolverSuite) TestResolvedRetryHooksStopRetryTimer(c *gc.C) { 381 // Resolving a failed hook should stop the retry timer. 382 s.testResolveHookErrorStopRetryTimer(c, params.ResolvedRetryHooks) 383 } 384 385 func (s *resolverSuite) TestResolvedNoHooksStopRetryTimer(c *gc.C) { 386 // Resolving a failed hook should stop the retry timer. 387 s.testResolveHookErrorStopRetryTimer(c, params.ResolvedNoHooks) 388 } 389 390 func (s *resolverSuite) testResolveHookErrorStopRetryTimer(c *gc.C, mode params.ResolvedMode) { 391 s.stub.ResetCalls() 392 s.clearResolved = func() error { return nil } 393 s.reportHookError = func(hook.Info) error { return nil } 394 localState := resolver.LocalState{ 395 CharmModifiedVersion: s.charmModifiedVersion, 396 CharmURL: s.charmURL, 397 State: operation.State{ 398 Kind: operation.RunHook, 399 Step: operation.Pending, 400 Installed: true, 401 Started: true, 402 Hook: &hook.Info{ 403 Kind: hooks.ConfigChanged, 404 }, 405 }, 406 } 407 408 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 409 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 410 s.stub.CheckCallNames(c, "StartRetryHookTimer") 411 412 s.remoteState.ResolvedMode = mode 413 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 414 c.Assert(err, jc.ErrorIsNil) 415 s.stub.CheckCallNames(c, "StartRetryHookTimer", "StopRetryHookTimer") 416 } 417 418 func (s *resolverSuite) TestRunHookStopRetryTimer(c *gc.C) { 419 s.reportHookError = func(hook.Info) error { return nil } 420 localState := resolver.LocalState{ 421 CharmModifiedVersion: s.charmModifiedVersion, 422 CharmURL: s.charmURL, 423 State: operation.State{ 424 Kind: operation.RunHook, 425 Step: operation.Pending, 426 Installed: true, 427 Started: true, 428 Hook: &hook.Info{ 429 Kind: hooks.ConfigChanged, 430 }, 431 }, 432 } 433 434 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 435 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 436 s.stub.CheckCallNames(c, "StartRetryHookTimer") 437 438 localState.Kind = operation.Continue 439 _, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory) 440 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 441 s.stub.CheckCallNames(c, "StartRetryHookTimer", "StopRetryHookTimer") 442 } 443 444 func (s *resolverSuite) TestRunsConfigChangedIfConfigHashChanges(c *gc.C) { 445 localState := resolver.LocalState{ 446 CharmModifiedVersion: s.charmModifiedVersion, 447 CharmURL: s.charmURL, 448 State: operation.State{ 449 Kind: operation.Continue, 450 Installed: true, 451 Started: true, 452 ConfigHash: "somehash", 453 }, 454 } 455 s.remoteState.ConfigHash = "differenthash" 456 457 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 458 c.Assert(err, jc.ErrorIsNil) 459 c.Assert(op.String(), gc.Equals, "run config-changed hook") 460 } 461 462 func (s *resolverSuite) TestRunsConfigChangedIfTrustHashChanges(c *gc.C) { 463 localState := resolver.LocalState{ 464 CharmModifiedVersion: s.charmModifiedVersion, 465 CharmURL: s.charmURL, 466 State: operation.State{ 467 Kind: operation.Continue, 468 Installed: true, 469 Started: true, 470 TrustHash: "somehash", 471 }, 472 } 473 s.remoteState.TrustHash = "differenthash" 474 475 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 476 c.Assert(err, jc.ErrorIsNil) 477 c.Assert(op.String(), gc.Equals, "run config-changed hook") 478 } 479 480 func (s *resolverSuite) TestRunsConfigChangedIfAddressesHashChanges(c *gc.C) { 481 localState := resolver.LocalState{ 482 CharmModifiedVersion: s.charmModifiedVersion, 483 CharmURL: s.charmURL, 484 State: operation.State{ 485 Kind: operation.Continue, 486 Installed: true, 487 Started: true, 488 AddressesHash: "somehash", 489 }, 490 } 491 s.remoteState.AddressesHash = "differenthash" 492 493 op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 494 c.Assert(err, jc.ErrorIsNil) 495 c.Assert(op.String(), gc.Equals, "run config-changed hook") 496 } 497 498 func (s *resolverSuite) TestNoOperationIfHashesAllMatch(c *gc.C) { 499 localState := resolver.LocalState{ 500 CharmModifiedVersion: s.charmModifiedVersion, 501 CharmURL: s.charmURL, 502 State: operation.State{ 503 Kind: operation.Continue, 504 Installed: true, 505 Started: true, 506 ConfigHash: "config", 507 TrustHash: "trust", 508 AddressesHash: "addresses", 509 }, 510 } 511 s.remoteState.ConfigHash = "config" 512 s.remoteState.TrustHash = "trust" 513 s.remoteState.AddressesHash = "addresses" 514 515 _, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory) 516 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 517 }