1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
10
"github.com/juju/errors"
11
"github.com/juju/loggo"
12
jc "github.com/juju/testing/checkers"
13
"github.com/juju/utils"
14
"github.com/juju/utils/arch"
15
"github.com/juju/utils/series"
16
gc "gopkg.in/check.v1"
17
"gopkg.in/juju/names.v2"
19
"github.com/juju/juju/agent"
20
cmdutil "github.com/juju/juju/cmd/jujud/util"
21
"github.com/juju/juju/constraints"
22
"github.com/juju/juju/environs"
23
"github.com/juju/juju/instance"
24
"github.com/juju/juju/mongo/mongotest"
25
"github.com/juju/juju/state"
26
"github.com/juju/juju/state/multiwatcher"
27
"github.com/juju/juju/state/stateenvirons"
28
statetesting "github.com/juju/juju/state/testing"
29
"github.com/juju/juju/status"
30
coretesting "github.com/juju/juju/testing"
31
"github.com/juju/juju/testing/factory"
32
"github.com/juju/juju/upgrades"
33
jujuversion "github.com/juju/juju/version"
34
"github.com/juju/juju/worker"
35
"github.com/juju/juju/worker/gate"
36
"github.com/juju/version"
39
// TODO(mjs) - these tests are too tightly coupled to the
40
// implementation. They needn't be internal tests.
42
type UpgradeSuite struct {
43
statetesting.StateSuite
45
oldVersion version.Binary
46
logWriter loggo.TestWriter
52
var _ = gc.Suite(&UpgradeSuite{})
55
const succeeds = false
57
func (s *UpgradeSuite) SetUpTest(c *gc.C) {
58
s.StateSuite.SetUpTest(c)
60
s.preUpgradeError = false
61
// Most of these tests normally finish sub-second on a fast machine.
62
// If any given test hits a minute, we have almost certainly become
63
// wedged, so dump the logs.
64
coretesting.DumpTestLogsAfter(time.Minute, c, s)
66
s.oldVersion = version.Binary{
67
Number: jujuversion.Current,
68
Arch: arch.HostArch(),
69
Series: series.HostSeries(),
71
s.oldVersion.Major = 1
72
s.oldVersion.Minor = 16
74
// Don't wait so long in tests.
75
s.PatchValue(&UpgradeStartTimeoutMaster, time.Duration(time.Millisecond*50))
76
s.PatchValue(&UpgradeStartTimeoutSecondary, time.Duration(time.Millisecond*60))
78
// Allow tests to make the API connection appear to be dead.
79
s.connectionDead = false
80
s.PatchValue(&cmdutil.ConnectionIsDead, func(loggo.Logger, cmdutil.Pinger) bool {
81
return s.connectionDead
84
s.machineIsMaster = true
85
fakeIsMachineMaster := func(*state.State, string) (bool, error) {
86
return s.machineIsMaster, nil
88
s.PatchValue(&IsMachineMaster, fakeIsMachineMaster)
92
func (s *UpgradeSuite) captureLogs(c *gc.C) {
93
c.Assert(loggo.RegisterWriter("upgrade-tests", &s.logWriter), gc.IsNil)
94
s.AddCleanup(func(*gc.C) {
95
loggo.RemoveWriter("upgrade-tests")
100
func (s *UpgradeSuite) countUpgradeAttempts(upgradeErr error) *int {
102
s.PatchValue(&PerformUpgrade, func(version.Number, []upgrades.Target, upgrades.Context) error {
109
func (s *UpgradeSuite) TestNewChannelWhenNoUpgradeRequired(c *gc.C) {
110
// Set the agent's initial upgradedToVersion to almost the same as
111
// the current version. We want it to be different to
112
// jujuversion.Current (so that we can see it change) but not to
113
// trigger upgrade steps.
114
config := NewFakeConfigSetter(names.NewMachineTag("0"), makeBumpedCurrentVersion().Number)
115
agent := NewFakeAgent(config)
117
lock, err := NewLock(agent)
118
c.Assert(err, jc.ErrorIsNil)
120
c.Assert(lock.IsUnlocked(), jc.IsTrue)
121
// The agent's version should have been updated.
122
c.Assert(config.Version, gc.Equals, jujuversion.Current)
126
func (s *UpgradeSuite) TestNewChannelWhenUpgradeRequired(c *gc.C) {
127
// Set the agent's upgradedToVersion so that upgrade steps are required.
128
initialVersion := version.MustParse("1.16.0")
129
config := NewFakeConfigSetter(names.NewMachineTag("0"), initialVersion)
130
agent := NewFakeAgent(config)
132
lock, err := NewLock(agent)
133
c.Assert(err, jc.ErrorIsNil)
135
c.Assert(lock.IsUnlocked(), jc.IsFalse)
136
// The agent's version should NOT have been updated.
137
c.Assert(config.Version, gc.Equals, initialVersion)
140
func (s *UpgradeSuite) TestRetryStrategy(c *gc.C) {
141
retries := getUpgradeRetryStrategy()
142
c.Assert(retries.Delay, gc.Equals, 2*time.Minute)
143
c.Assert(retries.Min, gc.Equals, 5)
146
func (s *UpgradeSuite) TestNoUpgradeNecessary(c *gc.C) {
147
attemptsP := s.countUpgradeAttempts(nil)
149
s.oldVersion.Number = jujuversion.Current // nothing to do
151
workerErr, config, _, doneLock := s.runUpgradeWorker(c, multiwatcher.JobHostUnits)
153
c.Check(workerErr, gc.IsNil)
154
c.Check(*attemptsP, gc.Equals, 0)
155
c.Check(config.Version, gc.Equals, jujuversion.Current)
156
c.Check(doneLock.IsUnlocked(), jc.IsTrue)
159
func (s *UpgradeSuite) TestUpgradeStepsFailure(c *gc.C) {
160
// This test checks what happens when every upgrade attempt fails.
161
// A number of retries should be observed and the agent should end
162
// up in a state where it is is still running but is reporting an
163
// error and the upgrade is not flagged as having completed (which
164
// prevents most of the agent's workers from running and keeps the
165
// API in restricted mode).
167
attemptsP := s.countUpgradeAttempts(errors.New("boom"))
170
workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, multiwatcher.JobHostUnits)
172
// The worker shouldn't return an error so that the worker and
173
// agent keep running.
174
c.Check(workerErr, gc.IsNil)
176
c.Check(*attemptsP, gc.Equals, maxUpgradeRetries)
177
c.Check(config.Version, gc.Equals, s.oldVersion.Number) // Upgrade didn't finish
178
c.Assert(statusCalls, jc.DeepEquals,
179
s.makeExpectedStatusCalls(maxUpgradeRetries-1, fails, "boom"))
180
c.Assert(s.logWriter.Log(), jc.LogMatches,
181
s.makeExpectedUpgradeLogs(maxUpgradeRetries-1, "hostMachine", fails, "boom"))
182
c.Assert(doneLock.IsUnlocked(), jc.IsFalse)
185
func (s *UpgradeSuite) TestUpgradeStepsRetries(c *gc.C) {
186
// This test checks what happens when the first upgrade attempt
187
// fails but the following on succeeds. The final state should be
188
// the same as a successful upgrade which worked first go.
191
fakePerformUpgrade := func(version.Number, []upgrades.Target, upgrades.Context) error {
195
return errors.New("boom")
200
s.PatchValue(&PerformUpgrade, fakePerformUpgrade)
203
workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, multiwatcher.JobHostUnits)
205
c.Check(workerErr, gc.IsNil)
206
c.Check(attempts, gc.Equals, 2)
207
c.Check(config.Version, gc.Equals, jujuversion.Current) // Upgrade finished
208
c.Assert(statusCalls, jc.DeepEquals, s.makeExpectedStatusCalls(1, succeeds, "boom"))
209
c.Assert(s.logWriter.Log(), jc.LogMatches, s.makeExpectedUpgradeLogs(1, "hostMachine", succeeds, "boom"))
210
c.Check(doneLock.IsUnlocked(), jc.IsTrue)
213
func (s *UpgradeSuite) TestOtherUpgradeRunFailure(c *gc.C) {
214
// This test checks what happens something other than the upgrade
215
// steps themselves fails, ensuring the something is logged and
216
// the agent status is updated.
218
fakePerformUpgrade := func(version.Number, []upgrades.Target, upgrades.Context) error {
219
// Delete UpgradeInfo for the upgrade so that finaliseUpgrade() will fail
220
s.State.ClearUpgradeInfo()
223
s.PatchValue(&PerformUpgrade, fakePerformUpgrade)
224
s.Factory.MakeMachine(c, &factory.MachineParams{
225
Jobs: []state.MachineJob{state.JobManageModel},
229
workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, multiwatcher.JobManageModel)
231
c.Check(workerErr, gc.IsNil)
232
c.Check(config.Version, gc.Equals, jujuversion.Current) // Upgrade almost finished
233
failReason := `upgrade done but: cannot set upgrade status to "finishing": ` +
234
`Another status change may have occurred concurrently`
235
c.Assert(statusCalls, jc.DeepEquals,
236
s.makeExpectedStatusCalls(0, fails, failReason))
237
c.Assert(s.logWriter.Log(), jc.LogMatches,
238
s.makeExpectedUpgradeLogs(0, "databaseMaster", fails, failReason))
239
c.Assert(doneLock.IsUnlocked(), jc.IsFalse)
242
func (s *UpgradeSuite) TestApiConnectionFailure(c *gc.C) {
243
// This test checks what happens when an upgrade fails because the
244
// connection to mongo has gone away. This will happen when the
245
// mongo master changes. In this case we want the upgrade worker
246
// to return immediately without further retries. The error should
247
// be returned by the worker so that the agent will restart.
249
attemptsP := s.countUpgradeAttempts(errors.New("boom"))
250
s.connectionDead = true // Make the connection to state appear to be dead
253
workerErr, config, _, doneLock := s.runUpgradeWorker(c, multiwatcher.JobHostUnits)
255
c.Check(workerErr, gc.ErrorMatches, "API connection lost during upgrade: boom")
256
c.Check(*attemptsP, gc.Equals, 1)
257
c.Check(config.Version, gc.Equals, s.oldVersion.Number) // Upgrade didn't finish
258
c.Assert(doneLock.IsUnlocked(), jc.IsFalse)
261
func (s *UpgradeSuite) TestAbortWhenOtherControllerDoesntStartUpgrade(c *gc.C) {
262
// This test checks when a controller is upgrading and one of
263
// the other controllers doesn't signal it is ready in time.
265
err := s.State.SetModelAgentVersion(jujuversion.Current)
266
c.Assert(err, jc.ErrorIsNil)
268
// The master controller in this scenario is functionally tested
270
s.machineIsMaster = false
272
s.create3Controllers(c)
274
attemptsP := s.countUpgradeAttempts(nil)
276
workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, multiwatcher.JobManageModel)
278
c.Check(workerErr, gc.IsNil)
279
c.Check(*attemptsP, gc.Equals, 0)
280
c.Check(config.Version, gc.Equals, s.oldVersion.Number) // Upgrade didn't happen
281
c.Assert(doneLock.IsUnlocked(), jc.IsFalse)
283
// The environment agent-version should still be the new version.
284
// It's up to the master to trigger the rollback.
285
s.assertEnvironAgentVersion(c, jujuversion.Current)
287
causeMsg := " timed out after 60ms"
288
c.Assert(s.logWriter.Log(), jc.LogMatches, []jc.SimpleMessage{
289
{loggo.INFO, "waiting for other controllers to be ready for upgrade"},
290
{loggo.ERROR, "aborted wait for other controllers: timed out after 60ms"},
291
{loggo.ERROR, `upgrade from .+ to .+ for "machine-0" failed \(giving up\): ` +
292
"aborted wait for other controllers:" + causeMsg},
294
c.Assert(statusCalls, jc.DeepEquals, []StatusCall{{
297
"upgrade to %s failed (giving up): aborted wait for other controllers:"+causeMsg,
298
jujuversion.Current),
302
func (s *UpgradeSuite) TestSuccessMaster(c *gc.C) {
303
// This test checks what happens when an upgrade works on the
304
// first attempt on a master controller.
305
s.machineIsMaster = true
306
info := s.checkSuccess(c, "databaseMaster", func(*state.UpgradeInfo) {})
307
c.Assert(info.Status(), gc.Equals, state.UpgradeFinishing)
310
func (s *UpgradeSuite) TestSuccessSecondary(c *gc.C) {
311
// This test checks what happens when an upgrade works on the
312
// first attempt on a secondary controller.
313
s.machineIsMaster = false
314
mungeInfo := func(info *state.UpgradeInfo) {
315
// Indicate that the master is done
316
err := info.SetStatus(state.UpgradeRunning)
317
c.Assert(err, jc.ErrorIsNil)
318
err = info.SetStatus(state.UpgradeFinishing)
319
c.Assert(err, jc.ErrorIsNil)
321
s.checkSuccess(c, "controller", mungeInfo)
324
func (s *UpgradeSuite) checkSuccess(c *gc.C, target string, mungeInfo func(*state.UpgradeInfo)) *state.UpgradeInfo {
325
_, machineIdB, machineIdC := s.create3Controllers(c)
327
// Indicate that machine B and C are ready to upgrade
328
vPrevious := s.oldVersion.Number
329
vNext := jujuversion.Current
330
info, err := s.State.EnsureUpgradeInfo(machineIdB, vPrevious, vNext)
331
c.Assert(err, jc.ErrorIsNil)
332
_, err = s.State.EnsureUpgradeInfo(machineIdC, vPrevious, vNext)
333
c.Assert(err, jc.ErrorIsNil)
337
attemptsP := s.countUpgradeAttempts(nil)
340
workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, multiwatcher.JobManageModel)
342
c.Check(workerErr, gc.IsNil)
343
c.Check(*attemptsP, gc.Equals, 1)
344
c.Check(config.Version, gc.Equals, jujuversion.Current) // Upgrade finished
345
c.Assert(statusCalls, jc.DeepEquals, s.makeExpectedStatusCalls(0, succeeds, ""))
346
c.Assert(s.logWriter.Log(), jc.LogMatches, s.makeExpectedUpgradeLogs(0, target, succeeds, ""))
347
c.Check(doneLock.IsUnlocked(), jc.IsTrue)
350
c.Assert(err, jc.ErrorIsNil)
351
c.Assert(info.ControllersDone(), jc.DeepEquals, []string{"0"})
355
func (s *UpgradeSuite) TestJobsToTargets(c *gc.C) {
356
check := func(jobs []multiwatcher.MachineJob, isMaster bool, expectedTargets ...upgrades.Target) {
357
c.Assert(jobsToTargets(jobs, isMaster), jc.SameContents, expectedTargets)
360
check([]multiwatcher.MachineJob{multiwatcher.JobHostUnits}, false, upgrades.HostMachine)
361
check([]multiwatcher.MachineJob{multiwatcher.JobManageModel}, false, upgrades.Controller)
362
check([]multiwatcher.MachineJob{multiwatcher.JobManageModel}, true,
363
upgrades.Controller, upgrades.DatabaseMaster)
364
check([]multiwatcher.MachineJob{multiwatcher.JobManageModel, multiwatcher.JobHostUnits}, false,
365
upgrades.Controller, upgrades.HostMachine)
366
check([]multiwatcher.MachineJob{multiwatcher.JobManageModel, multiwatcher.JobHostUnits}, true,
367
upgrades.Controller, upgrades.DatabaseMaster, upgrades.HostMachine)
370
func (s *UpgradeSuite) TestPreUpgradeFail(c *gc.C) {
371
s.preUpgradeError = true
374
workerErr, config, statusCalls, doneLock := s.runUpgradeWorker(c, multiwatcher.JobHostUnits)
376
c.Check(workerErr, jc.ErrorIsNil)
377
c.Check(config.Version, gc.Equals, s.oldVersion.Number) // Upgrade didn't finish
378
c.Assert(doneLock.IsUnlocked(), jc.IsFalse)
380
causeMessage := `machine 0 cannot be upgraded: preupgrade error`
381
failMessage := fmt.Sprintf(
382
`upgrade from %s to %s for "machine-0" failed \(giving up\): %s`,
383
s.oldVersion.Number, jujuversion.Current, causeMessage)
384
c.Assert(s.logWriter.Log(), jc.LogMatches, []jc.SimpleMessage{
385
{loggo.INFO, "checking that upgrade can proceed"},
386
{loggo.ERROR, failMessage},
389
statusMessage := fmt.Sprintf(
390
`upgrade to %s failed (giving up): %s`, jujuversion.Current, causeMessage)
391
c.Assert(statusCalls, jc.DeepEquals, []StatusCall{{
392
status.StatusError, statusMessage,
396
// Run just the upgradesteps worker with a fake machine agent and
397
// fake agent config.
398
func (s *UpgradeSuite) runUpgradeWorker(c *gc.C, jobs ...multiwatcher.MachineJob) (
399
error, *fakeConfigSetter, []StatusCall, gate.Lock,
401
s.setInstantRetryStrategy(c)
402
config := s.makeFakeConfig()
403
agent := NewFakeAgent(config)
404
doneLock, err := NewLock(agent)
405
c.Assert(err, jc.ErrorIsNil)
406
machineStatus := &testStatusSetter{}
407
worker, err := NewWorker(doneLock, agent, nil, jobs, s.openStateForUpgrade, s.preUpgradeSteps, machineStatus)
408
c.Assert(err, jc.ErrorIsNil)
409
return worker.Wait(), config, machineStatus.Calls, doneLock
412
func (s *UpgradeSuite) openStateForUpgrade() (*state.State, error) {
413
mongoInfo := s.State.MongoConnectionInfo()
414
newPolicy := stateenvirons.GetNewPolicyFunc(
415
stateenvirons.GetNewEnvironFunc(environs.New),
417
st, err := state.Open(s.State.ModelTag(), mongoInfo, mongotest.DialOpts(), newPolicy)
424
func (s *UpgradeSuite) preUpgradeSteps(st *state.State, agentConf agent.Config, isController, isMasterController bool) error {
425
if s.preUpgradeError {
426
return errors.New("preupgrade error")
431
func (s *UpgradeSuite) makeFakeConfig() *fakeConfigSetter {
432
return NewFakeConfigSetter(names.NewMachineTag("0"), s.oldVersion.Number)
435
func (s *UpgradeSuite) create3Controllers(c *gc.C) (machineIdA, machineIdB, machineIdC string) {
436
machine0 := s.Factory.MakeMachine(c, &factory.MachineParams{
437
Jobs: []state.MachineJob{state.JobManageModel},
439
machineIdA = machine0.Id()
440
s.setMachineAlive(c, machineIdA)
442
changes, err := s.State.EnableHA(3, constraints.Value{}, "quantal", nil)
443
c.Assert(err, jc.ErrorIsNil)
444
c.Assert(len(changes.Added), gc.Equals, 2)
446
machineIdB = changes.Added[0]
447
s.setMachineProvisioned(c, machineIdB)
448
s.setMachineAlive(c, machineIdB)
450
machineIdC = changes.Added[1]
451
s.setMachineProvisioned(c, machineIdC)
452
s.setMachineAlive(c, machineIdC)
457
func (s *UpgradeSuite) setMachineProvisioned(c *gc.C, id string) {
458
machine, err := s.State.Machine(id)
459
c.Assert(err, jc.ErrorIsNil)
460
err = machine.SetProvisioned(instance.Id(id+"-inst"), "nonce", nil)
461
c.Assert(err, jc.ErrorIsNil)
464
func (s *UpgradeSuite) setMachineAlive(c *gc.C, id string) {
465
machine, err := s.State.Machine(id)
466
c.Assert(err, jc.ErrorIsNil)
467
pinger, err := machine.SetAgentPresence()
468
c.Assert(err, jc.ErrorIsNil)
469
s.AddCleanup(func(c *gc.C) {
470
c.Assert(worker.Stop(pinger), jc.ErrorIsNil)
474
// Return a version the same as the current software version, but with
475
// the build number bumped.
477
// The version Tag is also cleared so that upgrades.PerformUpgrade
478
// doesn't think it needs to run upgrade steps unnecessarily.
479
func makeBumpedCurrentVersion() version.Binary {
481
Number: jujuversion.Current,
482
Arch: arch.HostArch(),
483
Series: series.HostSeries(),
490
const maxUpgradeRetries = 3
492
func (s *UpgradeSuite) setInstantRetryStrategy(c *gc.C) {
493
// TODO(katco): 2016-08-09: lp:1611427
494
s.PatchValue(&getUpgradeRetryStrategy, func() utils.AttemptStrategy {
495
c.Logf("setting instant retry strategy for upgrade: retries=%d", maxUpgradeRetries)
496
return utils.AttemptStrategy{
498
Min: maxUpgradeRetries,
503
func (s *UpgradeSuite) makeExpectedStatusCalls(retryCount int, expectFail bool, failReason string) []StatusCall {
504
calls := []StatusCall{{
505
status.StatusStarted,
506
fmt.Sprintf("upgrading to %s", jujuversion.Current),
508
for i := 0; i < retryCount; i++ {
509
calls = append(calls, StatusCall{
511
fmt.Sprintf("upgrade to %s failed (will retry): %s", jujuversion.Current, failReason),
515
calls = append(calls, StatusCall{
517
fmt.Sprintf("upgrade to %s failed (giving up): %s", jujuversion.Current, failReason),
520
calls = append(calls, StatusCall{status.StatusStarted, ""})
525
func (s *UpgradeSuite) makeExpectedUpgradeLogs(retryCount int, target string, expectFail bool, failReason string) []jc.SimpleMessage {
526
outLogs := []jc.SimpleMessage{}
528
if target == "databaseMaster" || target == "controller" {
529
outLogs = append(outLogs, jc.SimpleMessage{
530
loggo.INFO, "waiting for other controllers to be ready for upgrade",
534
case "databaseMaster":
535
waitMsg = "all controllers are ready to run upgrade steps"
537
waitMsg = "the master has completed its upgrade steps"
539
outLogs = append(outLogs, jc.SimpleMessage{loggo.INFO, "finished waiting - " + waitMsg})
542
outLogs = append(outLogs, jc.SimpleMessage{
543
loggo.INFO, fmt.Sprintf(
544
`starting upgrade from %s to %s for "machine-0"`,
545
s.oldVersion.Number, jujuversion.Current),
548
failMessage := fmt.Sprintf(
549
`upgrade from %s to %s for "machine-0" failed \(%%s\): %s`,
550
s.oldVersion.Number, jujuversion.Current, failReason)
552
for i := 0; i < retryCount; i++ {
553
outLogs = append(outLogs, jc.SimpleMessage{loggo.ERROR, fmt.Sprintf(failMessage, "will retry")})
556
outLogs = append(outLogs, jc.SimpleMessage{loggo.ERROR, fmt.Sprintf(failMessage, "giving up")})
558
outLogs = append(outLogs, jc.SimpleMessage{loggo.INFO,
559
fmt.Sprintf(`upgrade to %s completed successfully.`, jujuversion.Current)})
564
func (s *UpgradeSuite) assertEnvironAgentVersion(c *gc.C, expected version.Number) {
565
envConfig, err := s.State.ModelConfig()
566
c.Assert(err, jc.ErrorIsNil)
567
agentVersion, ok := envConfig.AgentVersion()
568
c.Assert(ok, jc.IsTrue)
569
c.Assert(agentVersion, gc.Equals, expected)
572
// NewFakeConfigSetter returns a fakeConfigSetter which implements
573
// just enough of the agent.ConfigSetter interface to keep the upgrade
574
// steps worker happy.
575
func NewFakeConfigSetter(agentTag names.Tag, initialVersion version.Number) *fakeConfigSetter {
576
return &fakeConfigSetter{
578
Version: initialVersion,
582
type fakeConfigSetter struct {
585
Version version.Number
588
func (s *fakeConfigSetter) Tag() names.Tag {
592
func (s *fakeConfigSetter) UpgradedToVersion() version.Number {
596
func (s *fakeConfigSetter) SetUpgradedToVersion(newVersion version.Number) {
597
s.Version = newVersion
600
// NewFakeAgent returns a fakeAgent which implements the agent.Agent
601
// interface. This provides enough MachineAgent functionality to
603
func NewFakeAgent(confSetter agent.ConfigSetter) *fakeAgent {
609
type fakeAgent struct {
610
config agent.ConfigSetter
613
func (a *fakeAgent) CurrentConfig() agent.Config {
617
func (a *fakeAgent) ChangeConfig(mutate agent.ConfigMutator) error {
618
return mutate(a.config)
621
type StatusCall struct {
626
type testStatusSetter struct {
630
func (s *testStatusSetter) SetStatus(status status.Status, info string, _ map[string]interface{}) error {
631
s.Calls = append(s.Calls, StatusCall{status, info})