github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/upgradesteps/worker_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package upgradesteps 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names/v5" 14 "github.com/juju/retry" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/version/v2" 17 gc "gopkg.in/check.v1" 18 19 "github.com/juju/juju/agent" 20 agenterrors "github.com/juju/juju/cmd/jujud/agent/errors" 21 "github.com/juju/juju/core/constraints" 22 "github.com/juju/juju/core/instance" 23 "github.com/juju/juju/core/status" 24 "github.com/juju/juju/state" 25 "github.com/juju/juju/state/stateenvirons" 26 statetesting "github.com/juju/juju/state/testing" 27 coretesting "github.com/juju/juju/testing" 28 "github.com/juju/juju/testing/factory" 29 "github.com/juju/juju/upgrades" 30 jujuversion "github.com/juju/juju/version" 31 "github.com/juju/juju/worker/gate" 32 ) 33 34 // TODO(mjs) - these tests are too tightly coupled to the 35 // implementation. They needn't be internal tests. 36 37 type UpgradeSuite struct { 38 statetesting.StateSuite 39 40 oldVersion version.Binary 41 logWriter loggo.TestWriter 42 connectionDead bool 43 preUpgradeError bool 44 } 45 46 var _ = gc.Suite(&UpgradeSuite{}) 47 48 const fails = true 49 const succeeds = false 50 51 func (s *UpgradeSuite) SetUpTest(c *gc.C) { 52 s.StateSuite.SetUpTest(c) 53 54 s.preUpgradeError = false 55 // Most of these tests normally finish sub-second on a fast machine. 56 // If any given test hits a minute, we have almost certainly become 57 // wedged, so dump the logs. 58 coretesting.DumpTestLogsAfter(time.Minute, c, s) 59 60 s.oldVersion = coretesting.CurrentVersion() 61 s.oldVersion.Major = 1 62 s.oldVersion.Minor = 16 63 64 // Don't wait so long in tests. 65 s.PatchValue(&UpgradeStartTimeoutController, time.Millisecond*50) 66 67 // Allow tests to make the API connection appear to be dead. 68 s.connectionDead = false 69 s.PatchValue(&agenterrors.ConnectionIsDead, func(loggo.Logger, agenterrors.Breakable) bool { 70 return s.connectionDead 71 }) 72 } 73 74 func (s *UpgradeSuite) captureLogs(c *gc.C) { 75 c.Assert(loggo.RegisterWriter("upgrade-tests", &s.logWriter), gc.IsNil) 76 s.AddCleanup(func(*gc.C) { 77 loggo.RemoveWriter("upgrade-tests") 78 s.logWriter.Clear() 79 }) 80 } 81 82 func (s *UpgradeSuite) countUpgradeAttempts(upgradeErr error) *int { 83 count := 0 84 s.PatchValue(&PerformUpgrade, func(version.Number, []upgrades.Target, upgrades.Context) error { 85 count++ 86 return upgradeErr 87 }) 88 return &count 89 } 90 91 func (s *UpgradeSuite) TestNewChannelWhenNoUpgradeRequired(c *gc.C) { 92 // Set the agent's upgradedToVersion to version.Current, 93 // to simulate the upgrade steps having been run already. 94 initialVersion := jujuversion.Current 95 config := NewFakeConfigSetter(names.NewMachineTag("0"), initialVersion) 96 97 lock := NewLock(config) 98 99 // Upgrade steps have already been run. 100 c.Assert(lock.IsUnlocked(), jc.IsTrue) 101 } 102 103 func (s *UpgradeSuite) TestNewChannelWhenUpgradeRequired(c *gc.C) { 104 // Set the agent's upgradedToVersion so that upgrade steps are required. 105 initialVersion := version.MustParse("1.16.0") 106 config := NewFakeConfigSetter(names.NewMachineTag("0"), initialVersion) 107 108 lock := NewLock(config) 109 110 c.Assert(lock.IsUnlocked(), jc.IsFalse) 111 // The agent's version should NOT have been updated. 112 c.Assert(config.Version, gc.Equals, initialVersion) 113 } 114 115 func (s *UpgradeSuite) TestNoUpgradeNecessary(c *gc.C) { 116 attemptsP := s.countUpgradeAttempts(nil) 117 s.captureLogs(c) 118 s.oldVersion.Number = jujuversion.Current // nothing to do 119 120 workerErr, config, _, doneLock := s.runUpgradeWorker(c, false) 121 122 c.Check(workerErr, gc.IsNil) 123 c.Check(*attemptsP, gc.Equals, 0) 124 c.Check(config.Version, gc.Equals, jujuversion.Current) 125 c.Check(doneLock.IsUnlocked(), jc.IsTrue) 126 } 127 128 func (s *UpgradeSuite) TestNoUpgradeNecessaryIgnoresBuildNumbers(c *gc.C) { 129 attemptsP := s.countUpgradeAttempts(nil) 130 s.captureLogs(c) 131 s.oldVersion.Number = jujuversion.Current 132 s.oldVersion.Build = 1 // Ensure there's a build number mismatch. 133 134 workerErr, config, _, doneLock := s.runUpgradeWorker(c, false) 135 136 c.Check(workerErr, gc.IsNil) 137 c.Check(*attemptsP, gc.Equals, 0) 138 c.Check(config.Version, gc.Equals, s.oldVersion.Number) 139 c.Check(doneLock.IsUnlocked(), jc.IsTrue) 140 } 141 142 func (s *UpgradeSuite) TestUpgradeStepsFailure(c *gc.C) { 143 // This test checks what happens when every upgrade attempt fails. 144 // A number of retries should be observed and the agent should end 145 // up in a state where it is is still running but is reporting an 146 // error and the upgrade is not flagged as having completed (which 147 // prevents most of the agent's workers from running and keeps the 148 // API in restricted mode). 149 150 attemptsP := s.countUpgradeAttempts(errors.New("boom")) 151 s.captureLogs(c) 152 153 workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, false) 154 155 // The worker shouldn't return an error so that the worker and 156 // agent keep running. 157 c.Check(workerErr, gc.IsNil) 158 159 c.Check(*attemptsP, gc.Equals, maxUpgradeRetries) 160 c.Check(config.Version, gc.Equals, s.oldVersion.Number) // Upgrade didn't finish 161 c.Assert(statusCalls, jc.DeepEquals, 162 s.makeExpectedStatusCalls(maxUpgradeRetries-1, fails, "boom")) 163 c.Assert(s.logWriter.Log(), jc.LogMatches, 164 s.makeExpectedUpgradeLogs(maxUpgradeRetries-1, "hostMachine", fails, "boom")) 165 c.Assert(doneLock.IsUnlocked(), jc.IsFalse) 166 } 167 168 func (s *UpgradeSuite) TestUpgradeStepsRetries(c *gc.C) { 169 // This test checks what happens when the first upgrade attempt 170 // fails but the following on succeeds. The final state should be 171 // the same as a successful upgrade which worked first go. 172 attempts := 0 173 fail := true 174 fakePerformUpgrade := func(version.Number, []upgrades.Target, upgrades.Context) error { 175 attempts++ 176 if fail { 177 fail = false 178 return errors.New("boom") 179 } else { 180 return nil 181 } 182 } 183 s.PatchValue(&PerformUpgrade, fakePerformUpgrade) 184 s.captureLogs(c) 185 186 workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, false) 187 188 c.Check(workerErr, gc.IsNil) 189 c.Check(attempts, gc.Equals, 2) 190 c.Check(config.Version, gc.Equals, jujuversion.Current) // Upgrade finished 191 c.Assert(statusCalls, jc.DeepEquals, s.makeExpectedStatusCalls(1, succeeds, "boom")) 192 c.Assert(s.logWriter.Log(), jc.LogMatches, s.makeExpectedUpgradeLogs(1, "hostMachine", succeeds, "boom")) 193 c.Check(doneLock.IsUnlocked(), jc.IsTrue) 194 } 195 196 func (s *UpgradeSuite) TestOtherUpgradeRunFailure(c *gc.C) { 197 // This test checks what happens something other than the upgrade 198 // steps themselves fails, ensuring the something is logged and 199 // the agent status is updated. 200 201 m := s.Factory.MakeMachine(c, &factory.MachineParams{ 202 Jobs: []state.MachineJob{state.JobManageModel}, 203 }) 204 s.captureLogs(c) 205 206 // Simulate the upgrade-database worker having run successfully. 207 info, err := s.State.EnsureUpgradeInfo(m.Id(), s.oldVersion.Number, jujuversion.Current) 208 c.Assert(err, jc.ErrorIsNil) 209 err = info.SetStatus(state.UpgradeDBComplete) 210 c.Assert(err, jc.ErrorIsNil) 211 212 fakePerformUpgrade := func(version.Number, []upgrades.Target, upgrades.Context) error { 213 // Violate the state-machine rules so that finaliseUpgrade() will fail. 214 // Recreating the upgrade doc will put us into status "pending" without 215 // any recorded controller ready/completed entries. 216 if err := s.State.ClearUpgradeInfo(); err != nil { 217 return err 218 } 219 info, err = s.State.EnsureUpgradeInfo(m.Id(), s.oldVersion.Number, jujuversion.Current) 220 return err 221 } 222 s.PatchValue(&PerformUpgrade, fakePerformUpgrade) 223 224 workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, true) 225 c.Check(workerErr, gc.IsNil) 226 227 c.Check(config.Version, gc.Equals, jujuversion.Current) // Upgrade almost finished 228 229 failReason := `upgrade done but failed to synchronise: cannot complete upgrade: upgrade has not yet run` 230 c.Assert(statusCalls, jc.DeepEquals, s.makeExpectedStatusCalls(0, fails, failReason)) 231 c.Assert(s.logWriter.Log(), jc.LogMatches, s.makeExpectedUpgradeLogs(0, "databaseMaster", fails, failReason)) 232 c.Assert(doneLock.IsUnlocked(), jc.IsFalse) 233 } 234 235 func (s *UpgradeSuite) TestAPIConnectionFailure(c *gc.C) { 236 // This test checks what happens when an upgrade fails because the 237 // connection to mongo has gone away. This will happen when the 238 // mongo master changes. In this case we want the upgrade worker 239 // to return immediately without further retries. The error should 240 // be returned by the worker so that the agent will restart. 241 242 attemptsP := s.countUpgradeAttempts(errors.New("boom")) 243 s.connectionDead = true // Make the connection to state appear to be dead 244 s.captureLogs(c) 245 246 workerErr, config, _, doneLock := s.runUpgradeWorker(c, false) 247 248 c.Check(workerErr, gc.ErrorMatches, "API connection lost during upgrade: boom") 249 c.Check(*attemptsP, gc.Equals, 1) 250 c.Check(config.Version, gc.Equals, s.oldVersion.Number) // Upgrade didn't finish 251 c.Assert(doneLock.IsUnlocked(), jc.IsFalse) 252 } 253 254 func (s *UpgradeSuite) TestAbortWhenOtherControllerDoesNotStartUpgrade(c *gc.C) { 255 // This test checks when a controller is upgrading and one of 256 // the other controllers doesn't signal it is ready in time. 257 258 err := s.State.SetModelAgentVersion(jujuversion.Current, nil, false) 259 c.Assert(err, jc.ErrorIsNil) 260 261 s.create3Controllers(c) 262 s.captureLogs(c) 263 attemptsP := s.countUpgradeAttempts(nil) 264 265 workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, true) 266 267 c.Check(workerErr, gc.IsNil) 268 c.Check(*attemptsP, gc.Equals, 0) 269 c.Check(config.Version, gc.Equals, s.oldVersion.Number) // Upgrade didn't happen 270 c.Assert(doneLock.IsUnlocked(), jc.IsFalse) 271 272 // The environment agent-version should still be the new version. 273 // It's up to the master to trigger the rollback. 274 s.assertEnvironAgentVersion(c, jujuversion.Current) 275 276 causeMsg := " timed out after 50ms" 277 c.Assert(s.logWriter.Log(), jc.LogMatches, []jc.SimpleMessage{ 278 {loggo.INFO, "waiting for other controllers to be ready for upgrade"}, 279 {loggo.ERROR, "aborted wait for other controllers: timed out after 50ms"}, 280 {loggo.ERROR, `upgrade from .+ to .+ for "machine-0" failed \(giving up\): ` + 281 "aborted wait for other controllers:" + causeMsg}, 282 }) 283 c.Assert(statusCalls, jc.DeepEquals, []StatusCall{{ 284 status.Error, 285 fmt.Sprintf( 286 "upgrade to %s failed (giving up): aborted wait for other controllers:"+causeMsg, 287 jujuversion.Current), 288 }}) 289 } 290 291 func (s *UpgradeSuite) TestSuccessLeadingController(c *gc.C) { 292 // This test checks what happens when an upgrade works on the first 293 // attempt, on the first controller to set the status to "running". 294 info := s.checkSuccess(c, "databaseMaster", func(i *state.UpgradeInfo) { 295 err := i.SetStatus(state.UpgradeDBComplete) 296 c.Assert(err, jc.ErrorIsNil) 297 }) 298 c.Assert(info.Status(), gc.Equals, state.UpgradeRunning) 299 } 300 301 func (s *UpgradeSuite) TestSuccessFollowingController(c *gc.C) { 302 // This test checks what happens when an upgrade works on the a controller 303 // following a controller having already set the status to "running". 304 s.checkSuccess(c, "controller", func(info *state.UpgradeInfo) { 305 // Indicate that the master is done 306 err := info.SetStatus(state.UpgradeDBComplete) 307 c.Assert(err, jc.ErrorIsNil) 308 err = info.SetStatus(state.UpgradeRunning) 309 c.Assert(err, jc.ErrorIsNil) 310 }) 311 } 312 313 func (s *UpgradeSuite) checkSuccess(c *gc.C, target string, mungeInfo func(*state.UpgradeInfo)) *state.UpgradeInfo { 314 _, machineIdB, machineIdC := s.create3Controllers(c) 315 316 // Indicate that machine B and C are ready to upgrade 317 vPrevious := s.oldVersion.Number 318 vNext := jujuversion.Current 319 info, err := s.State.EnsureUpgradeInfo(machineIdB, vPrevious, vNext) 320 c.Assert(err, jc.ErrorIsNil) 321 _, err = s.State.EnsureUpgradeInfo(machineIdC, vPrevious, vNext) 322 c.Assert(err, jc.ErrorIsNil) 323 324 mungeInfo(info) 325 326 attemptsP := s.countUpgradeAttempts(nil) 327 s.captureLogs(c) 328 329 workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, true) 330 331 c.Check(workerErr, gc.IsNil) 332 c.Check(*attemptsP, gc.Equals, 1) 333 c.Check(config.Version, gc.Equals, jujuversion.Current) // Upgrade finished 334 c.Assert(statusCalls, jc.DeepEquals, s.makeExpectedStatusCalls(0, succeeds, "")) 335 c.Assert(s.logWriter.Log(), jc.LogMatches, s.makeExpectedUpgradeLogs(0, target, succeeds, "")) 336 c.Check(doneLock.IsUnlocked(), jc.IsTrue) 337 338 err = info.Refresh() 339 c.Assert(err, jc.ErrorIsNil) 340 c.Assert(info.ControllersDone(), jc.DeepEquals, []string{"0"}) 341 return info 342 } 343 344 func (s *UpgradeSuite) TestJobsToTargets(c *gc.C) { 345 c.Assert(upgradeTargets(false), jc.DeepEquals, []upgrades.Target{upgrades.HostMachine}) 346 c.Assert(upgradeTargets(true), jc.SameContents, []upgrades.Target{upgrades.HostMachine, upgrades.Controller}) 347 } 348 349 func (s *UpgradeSuite) TestPreUpgradeFail(c *gc.C) { 350 s.preUpgradeError = true 351 s.captureLogs(c) 352 353 workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, false) 354 355 c.Check(workerErr, jc.ErrorIsNil) 356 c.Check(config.Version, gc.Equals, s.oldVersion.Number) // Upgrade didn't finish 357 c.Assert(doneLock.IsUnlocked(), jc.IsFalse) 358 359 causeMessage := `machine 0 cannot be upgraded: preupgrade error` 360 failMessage := fmt.Sprintf( 361 `upgrade from %s to %s for "machine-0" failed \(giving up\): %s`, 362 s.oldVersion.Number, jujuversion.Current, causeMessage) 363 c.Assert(s.logWriter.Log(), jc.LogMatches, []jc.SimpleMessage{ 364 {loggo.INFO, "checking that upgrade can proceed"}, 365 {loggo.ERROR, failMessage}, 366 }) 367 368 statusMessage := fmt.Sprintf( 369 `upgrade to %s failed (giving up): %s`, jujuversion.Current, causeMessage) 370 c.Assert(statusCalls, jc.DeepEquals, []StatusCall{{ 371 status.Error, statusMessage, 372 }}) 373 } 374 375 // Run just the upgradeSteps worker with a fake machine agent and 376 // fake agent config. 377 func (s *UpgradeSuite) runUpgradeWorker(c *gc.C, isController bool) ( 378 error, *fakeConfigSetter, []StatusCall, gate.Lock, 379 ) { 380 config := s.makeFakeConfig() 381 agent := NewFakeAgent(config) 382 doneLock := NewLock(config) 383 machineStatus := &testStatusSetter{} 384 testRetryStrategy := retry.CallArgs{ 385 Clock: clock.WallClock, 386 Delay: time.Millisecond, 387 Attempts: maxUpgradeRetries, 388 } 389 worker, err := NewWorker( 390 doneLock, 391 agent, 392 nil, 393 isController, 394 s.openStateForUpgrade, 395 s.preUpgradeSteps, 396 testRetryStrategy, 397 machineStatus, 398 false, 399 ) 400 c.Assert(err, jc.ErrorIsNil) 401 return worker.Wait(), config, machineStatus.Calls, doneLock 402 } 403 404 func (s *UpgradeSuite) openStateForUpgrade() (*state.StatePool, error) { 405 newPolicy := stateenvirons.GetNewPolicyFunc() 406 pool, err := state.OpenStatePool(state.OpenParams{ 407 Clock: clock.WallClock, 408 ControllerTag: s.State.ControllerTag(), 409 ControllerModelTag: s.Model.ModelTag(), 410 MongoSession: s.State.MongoSession(), 411 NewPolicy: newPolicy, 412 }) 413 if err != nil { 414 return nil, err 415 } 416 return pool, nil 417 } 418 419 func (s *UpgradeSuite) preUpgradeSteps(_ *state.StatePool, _ agent.Config, _, _ bool) error { 420 if s.preUpgradeError { 421 return errors.New("preupgrade error") 422 } 423 return nil 424 } 425 426 func (s *UpgradeSuite) makeFakeConfig() *fakeConfigSetter { 427 return NewFakeConfigSetter(names.NewMachineTag("0"), s.oldVersion.Number) 428 } 429 430 func (s *UpgradeSuite) create3Controllers(c *gc.C) (machineIdA, machineIdB, machineIdC string) { 431 machine0 := s.Factory.MakeMachine(c, &factory.MachineParams{ 432 Jobs: []state.MachineJob{state.JobManageModel}, 433 }) 434 machineIdA = machine0.Id() 435 436 changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("12.10"), nil) 437 c.Assert(err, jc.ErrorIsNil) 438 c.Assert(len(changes.Added), gc.Equals, 2) 439 440 machineIdB = changes.Added[0] 441 s.setMachineProvisioned(c, machineIdB) 442 443 machineIdC = changes.Added[1] 444 s.setMachineProvisioned(c, machineIdC) 445 446 return 447 } 448 449 func (s *UpgradeSuite) setMachineProvisioned(c *gc.C, id string) { 450 machine, err := s.State.Machine(id) 451 c.Assert(err, jc.ErrorIsNil) 452 err = machine.SetProvisioned(instance.Id(id+"-inst"), "", "nonce", nil) 453 c.Assert(err, jc.ErrorIsNil) 454 } 455 456 const maxUpgradeRetries = 3 457 458 func (s *UpgradeSuite) makeExpectedStatusCalls(retryCount int, expectFail bool, failReason string) []StatusCall { 459 calls := []StatusCall{{ 460 status.Started, 461 fmt.Sprintf("upgrading to %s", jujuversion.Current), 462 }} 463 for i := 0; i < retryCount; i++ { 464 calls = append(calls, StatusCall{ 465 status.Error, 466 fmt.Sprintf("upgrade to %s failed (will retry): %s", jujuversion.Current, failReason), 467 }) 468 } 469 if expectFail { 470 calls = append(calls, StatusCall{ 471 status.Error, 472 fmt.Sprintf("upgrade to %s failed (giving up): %s", jujuversion.Current, failReason), 473 }) 474 } else { 475 calls = append(calls, StatusCall{status.Started, ""}) 476 } 477 return calls 478 } 479 480 func (s *UpgradeSuite) makeExpectedUpgradeLogs(retryCount int, target string, expectFail bool, failReason string) []jc.SimpleMessage { 481 outLogs := []jc.SimpleMessage{} 482 483 if target == "databaseMaster" || target == "controller" { 484 outLogs = append(outLogs, jc.SimpleMessage{ 485 Level: loggo.INFO, Message: "waiting for other controllers to be ready for upgrade", 486 }) 487 outLogs = append(outLogs, jc.SimpleMessage{ 488 Level: loggo.INFO, 489 Message: "finished waiting - all controllers are ready to run upgrade steps", 490 }) 491 } 492 493 outLogs = append(outLogs, jc.SimpleMessage{ 494 Level: loggo.INFO, Message: fmt.Sprintf( 495 `starting upgrade from %s to %s for "machine-0"`, 496 s.oldVersion.Number, jujuversion.Current), 497 }) 498 499 failMessage := fmt.Sprintf( 500 `upgrade from %s to %s for "machine-0" failed \(%%s\): %s`, 501 s.oldVersion.Number, jujuversion.Current, failReason) 502 503 for i := 0; i < retryCount; i++ { 504 outLogs = append(outLogs, jc.SimpleMessage{Level: loggo.ERROR, Message: fmt.Sprintf(failMessage, "will retry")}) 505 } 506 if expectFail { 507 outLogs = append(outLogs, jc.SimpleMessage{Level: loggo.ERROR, Message: fmt.Sprintf(failMessage, "giving up")}) 508 } else { 509 outLogs = append(outLogs, jc.SimpleMessage{Level: loggo.INFO, 510 Message: fmt.Sprintf(`upgrade to %s completed successfully.`, jujuversion.Current)}) 511 } 512 return outLogs 513 } 514 515 func (s *UpgradeSuite) assertEnvironAgentVersion(c *gc.C, expected version.Number) { 516 envConfig, err := s.Model.ModelConfig() 517 c.Assert(err, jc.ErrorIsNil) 518 agentVersion, ok := envConfig.AgentVersion() 519 c.Assert(ok, jc.IsTrue) 520 c.Assert(agentVersion, gc.Equals, expected) 521 } 522 523 // NewFakeConfigSetter returns a fakeConfigSetter which implements 524 // just enough of the agent.ConfigSetter interface to keep the upgrade 525 // steps worker happy. 526 func NewFakeConfigSetter(agentTag names.Tag, initialVersion version.Number) *fakeConfigSetter { 527 return &fakeConfigSetter{ 528 AgentTag: agentTag, 529 Version: initialVersion, 530 } 531 } 532 533 type fakeConfigSetter struct { 534 agent.ConfigSetter 535 AgentTag names.Tag 536 Version version.Number 537 } 538 539 func (s *fakeConfigSetter) Tag() names.Tag { 540 return s.AgentTag 541 } 542 543 func (s *fakeConfigSetter) UpgradedToVersion() version.Number { 544 return s.Version 545 } 546 547 func (s *fakeConfigSetter) SetUpgradedToVersion(newVersion version.Number) { 548 s.Version = newVersion 549 } 550 551 // NewFakeAgent returns a fakeAgent which implements the agent.Agent 552 // interface. This provides enough MachineAgent functionality to 553 // support upgrades. 554 func NewFakeAgent(confSetter agent.ConfigSetter) *fakeAgent { 555 return &fakeAgent{ 556 config: confSetter, 557 } 558 } 559 560 type fakeAgent struct { 561 config agent.ConfigSetter 562 } 563 564 func (a *fakeAgent) CurrentConfig() agent.Config { 565 return a.config 566 } 567 568 func (a *fakeAgent) ChangeConfig(mutate agent.ConfigMutator) error { 569 return mutate(a.config) 570 } 571 572 type StatusCall struct { 573 Status status.Status 574 Info string 575 } 576 577 type testStatusSetter struct { 578 Calls []StatusCall 579 } 580 581 func (s *testStatusSetter) SetStatus(status status.Status, info string, _ map[string]interface{}) error { 582 s.Calls = append(s.Calls, StatusCall{status, info}) 583 return nil 584 }