1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
13
jc "github.com/juju/testing/checkers"
14
"github.com/juju/version"
15
gc "gopkg.in/check.v1"
16
"gopkg.in/juju/names.v2"
18
"github.com/juju/juju/agent"
19
"github.com/juju/juju/api"
20
"github.com/juju/juju/apiserver/params"
21
"github.com/juju/juju/mongo"
22
"github.com/juju/juju/state"
23
"github.com/juju/juju/state/multiwatcher"
24
coretesting "github.com/juju/juju/testing"
25
"github.com/juju/juju/upgrades"
26
jujuversion "github.com/juju/juju/version"
29
func TestPackage(t *stdtesting.T) {
30
coretesting.MgoTestPackage(t)
33
// assertStateSteps is a helper that ensures that the given
34
// state-based upgrade steps match what is expected for that version
35
// and that the steps have been added to the global upgrade operations
37
func assertStateSteps(c *gc.C, ver version.Number, expectedSteps []string) {
38
findAndCheckSteps(c, (*upgrades.StateUpgradeOperations)(), ver, expectedSteps)
41
// assertSteps is a helper that ensures that the given API-based
42
// upgrade steps match what is expected for that version and that the
43
// steps have been added to the global upgrade operations list.
44
func assertSteps(c *gc.C, ver version.Number, expectedSteps []string) {
45
findAndCheckSteps(c, (*upgrades.UpgradeOperations)(), ver, expectedSteps)
48
func findAndCheckSteps(c *gc.C, ops []upgrades.Operation, ver version.Number, expectedSteps []string) {
49
for _, op := range ops {
50
if op.TargetVersion() == ver {
51
assertExpectedSteps(c, op.Steps(), expectedSteps)
55
if len(expectedSteps) > 0 {
56
c.Fatal("upgrade operations for this version are not hooked up")
60
// assertExpectedSteps is a helper function used to check that the upgrade steps match
61
// what is expected for a version.
62
func assertExpectedSteps(c *gc.C, steps []upgrades.Step, expectedSteps []string) {
63
c.Assert(steps, gc.HasLen, len(expectedSteps))
65
var stepNames = make([]string, len(steps))
66
for i, step := range steps {
67
stepNames[i] = step.Description()
69
c.Assert(stepNames, gc.DeepEquals, expectedSteps)
72
type upgradeSuite struct {
76
var _ = gc.Suite(&upgradeSuite{})
78
type mockUpgradeOperation struct {
79
targetVersion version.Number
83
func (m *mockUpgradeOperation) TargetVersion() version.Number {
84
return m.targetVersion
87
func (m *mockUpgradeOperation) Steps() []upgrades.Step {
91
type mockUpgradeStep struct {
93
targets []upgrades.Target
96
func (u *mockUpgradeStep) Description() string {
100
func (u *mockUpgradeStep) Targets() []upgrades.Target {
104
func (u *mockUpgradeStep) Run(ctx upgrades.Context) error {
105
if strings.HasSuffix(u.msg, "error") {
106
return errors.New("upgrade error occurred")
108
context := ctx.(*mockContext)
109
context.messages = append(context.messages, u.msg)
113
func newUpgradeStep(msg string, targets ...upgrades.Target) *mockUpgradeStep {
114
if len(targets) < 1 {
115
panic(fmt.Sprintf("step %q must have at least one target", msg))
117
return &mockUpgradeStep{
123
type mockContext struct {
125
agentConfig *mockAgentConfig
126
realAgentConfig agent.ConfigSetter
127
apiState api.Connection
131
func (c *mockContext) APIState() api.Connection {
135
func (c *mockContext) State() *state.State {
139
func (c *mockContext) AgentConfig() agent.ConfigSetter {
140
if c.realAgentConfig != nil {
141
return c.realAgentConfig
146
func (c *mockContext) StateContext() upgrades.Context {
150
func (c *mockContext) APIContext() upgrades.Context {
154
type mockAgentConfig struct {
159
jobs []multiwatcher.MachineJob
160
apiAddresses []string
161
values map[string]string
162
mongoInfo *mongo.MongoInfo
163
servingInfo params.StateServingInfo
164
modelTag names.ModelTag
167
func (mock *mockAgentConfig) Tag() names.Tag {
171
func (mock *mockAgentConfig) DataDir() string {
175
func (mock *mockAgentConfig) LogDir() string {
179
func (mock *mockAgentConfig) SystemIdentityPath() string {
180
return filepath.Join(mock.dataDir, agent.SystemIdentity)
183
func (mock *mockAgentConfig) Jobs() []multiwatcher.MachineJob {
187
func (mock *mockAgentConfig) APIAddresses() ([]string, error) {
188
return mock.apiAddresses, nil
191
func (mock *mockAgentConfig) Value(name string) string {
192
return mock.values[name]
195
func (mock *mockAgentConfig) MongoInfo() (*mongo.MongoInfo, bool) {
196
return mock.mongoInfo, true
199
func (mock *mockAgentConfig) StateServingInfo() (params.StateServingInfo, bool) {
200
return mock.servingInfo, true
203
func (mock *mockAgentConfig) SetStateServingInfo(info params.StateServingInfo) {
204
mock.servingInfo = info
207
func (mock *mockAgentConfig) Model() names.ModelTag {
211
func stateUpgradeOperations() []upgrades.Operation {
212
steps := []upgrades.Operation{
213
&mockUpgradeOperation{
214
targetVersion: version.MustParse("1.11.0"),
215
steps: []upgrades.Step{
216
newUpgradeStep("state step 1 - 1.11.0", upgrades.Controller),
217
newUpgradeStep("state step 2 error", upgrades.Controller),
218
newUpgradeStep("state step 3 - 1.11.0", upgrades.Controller),
221
&mockUpgradeOperation{
222
targetVersion: version.MustParse("1.21.0"),
223
steps: []upgrades.Step{
224
newUpgradeStep("state step 1 - 1.21.0", upgrades.DatabaseMaster),
225
newUpgradeStep("state step 2 - 1.21.0", upgrades.Controller),
228
&mockUpgradeOperation{
229
targetVersion: version.MustParse("1.22.0"),
230
steps: []upgrades.Step{
231
newUpgradeStep("state step 1 - 1.22.0", upgrades.DatabaseMaster),
232
newUpgradeStep("state step 2 - 1.22.0", upgrades.Controller),
239
func upgradeOperations() []upgrades.Operation {
240
steps := []upgrades.Operation{
241
&mockUpgradeOperation{
242
targetVersion: version.MustParse("1.12.0"),
243
steps: []upgrades.Step{
244
newUpgradeStep("step 1 - 1.12.0", upgrades.AllMachines),
245
newUpgradeStep("step 2 error", upgrades.HostMachine),
246
newUpgradeStep("step 3", upgrades.HostMachine),
249
&mockUpgradeOperation{
250
targetVersion: version.MustParse("1.16.0"),
251
steps: []upgrades.Step{
252
newUpgradeStep("step 1 - 1.16.0", upgrades.HostMachine),
253
newUpgradeStep("step 2 - 1.16.0", upgrades.HostMachine),
254
newUpgradeStep("step 3 - 1.16.0", upgrades.Controller),
257
&mockUpgradeOperation{
258
targetVersion: version.MustParse("1.17.0"),
259
steps: []upgrades.Step{
260
newUpgradeStep("step 1 - 1.17.0", upgrades.HostMachine),
263
&mockUpgradeOperation{
264
targetVersion: version.MustParse("1.17.1"),
265
steps: []upgrades.Step{
266
newUpgradeStep("step 1 - 1.17.1", upgrades.HostMachine),
267
newUpgradeStep("step 2 - 1.17.1", upgrades.Controller),
270
&mockUpgradeOperation{
271
targetVersion: version.MustParse("1.18.0"),
272
steps: []upgrades.Step{
273
newUpgradeStep("step 1 - 1.18.0", upgrades.HostMachine),
274
newUpgradeStep("step 2 - 1.18.0", upgrades.Controller),
277
&mockUpgradeOperation{
278
targetVersion: version.MustParse("1.20.0"),
279
steps: []upgrades.Step{
280
newUpgradeStep("step 1 - 1.20.0", upgrades.AllMachines),
281
newUpgradeStep("step 2 - 1.20.0", upgrades.HostMachine),
282
newUpgradeStep("step 3 - 1.20.0", upgrades.Controller),
285
&mockUpgradeOperation{
286
targetVersion: version.MustParse("1.21.0"),
287
steps: []upgrades.Step{
288
newUpgradeStep("step 1 - 1.21.0", upgrades.AllMachines),
291
&mockUpgradeOperation{
292
targetVersion: version.MustParse("1.22.0"),
293
steps: []upgrades.Step{
294
// Separate targets used intentionally
295
newUpgradeStep("step 1 - 1.22.0", upgrades.Controller, upgrades.HostMachine),
296
newUpgradeStep("step 2 - 1.22.0", upgrades.AllMachines),
303
type areUpgradesDefinedTest struct {
311
var areUpgradesDefinedTests = []areUpgradesDefinedTest{
313
about: "no ops if same version",
314
fromVersion: "1.18.0",
318
about: "true when ops defined between versions",
319
fromVersion: "1.17.1",
323
about: "false when no ops defined between versions",
324
fromVersion: "1.13.0",
329
about: "true when just state ops defined ",
330
fromVersion: "1.10.0",
335
about: "from version is defaulted when not supplied",
340
about: "upgrade between pre-final versions",
341
fromVersion: "1.21-beta4",
342
toVersion: "1.21-beta5",
346
about: "no upgrades when version hasn't changed, even with release tags",
347
fromVersion: "1.21-beta5",
348
toVersion: "1.21-beta5",
353
func (s *upgradeSuite) TestAreUpgradesDefined(c *gc.C) {
354
s.PatchValue(upgrades.StateUpgradeOperations, stateUpgradeOperations)
355
s.PatchValue(upgrades.UpgradeOperations, upgradeOperations)
356
for i, test := range areUpgradesDefinedTests {
357
c.Logf("%d: %s", i, test.about)
358
fromVersion := version.Zero
359
if test.fromVersion != "" {
360
fromVersion = version.MustParse(test.fromVersion)
362
toVersion := version.MustParse("1.18.0")
363
if test.toVersion != "" {
364
toVersion = version.MustParse(test.toVersion)
366
s.PatchValue(&jujuversion.Current, toVersion)
367
result := upgrades.AreUpgradesDefined(fromVersion)
368
c.Check(result, gc.Equals, test.expected)
372
type upgradeTest struct {
376
targets []upgrades.Target
377
expectedSteps []string
381
func targets(t ...upgrades.Target) []upgrades.Target {
385
var upgradeTests = []upgradeTest{
387
about: "from version excludes steps for same version",
388
fromVersion: "1.18.0",
389
targets: targets(upgrades.HostMachine),
390
expectedSteps: []string{},
393
about: "target version excludes steps for newer version",
395
targets: targets(upgrades.HostMachine),
396
expectedSteps: []string{"step 1 - 1.17.0", "step 1 - 1.17.1"},
399
about: "from version excludes older steps",
400
fromVersion: "1.17.0",
401
targets: targets(upgrades.HostMachine),
402
expectedSteps: []string{"step 1 - 1.17.1", "step 1 - 1.18.0"},
405
about: "incompatible targets excluded",
406
fromVersion: "1.17.1",
407
targets: targets(upgrades.Controller),
408
expectedSteps: []string{"step 2 - 1.18.0"},
411
about: "allMachines matches everything",
412
fromVersion: "1.18.1",
414
targets: targets(upgrades.HostMachine),
415
expectedSteps: []string{"step 1 - 1.20.0", "step 2 - 1.20.0"},
418
about: "allMachines matches everything",
419
fromVersion: "1.18.1",
421
targets: targets(upgrades.Controller),
422
expectedSteps: []string{"step 1 - 1.20.0", "step 3 - 1.20.0"},
425
about: "state step error aborts, subsequent state steps not run",
426
fromVersion: "1.10.0",
427
targets: targets(upgrades.Controller),
428
expectedSteps: []string{"state step 1 - 1.11.0"},
429
err: "state step 2 error: upgrade error occurred",
432
about: "error aborts, subsequent steps not run",
433
fromVersion: "1.11.0",
434
targets: targets(upgrades.HostMachine),
435
expectedSteps: []string{"step 1 - 1.12.0"},
436
err: "step 2 error: upgrade error occurred",
439
about: "default from version is 1.16",
441
targets: targets(upgrades.Controller),
442
expectedSteps: []string{"step 2 - 1.17.1", "step 2 - 1.18.0"},
445
about: "controllers don't get database master",
446
fromVersion: "1.20.0",
448
targets: targets(upgrades.Controller),
449
expectedSteps: []string{"state step 2 - 1.21.0", "step 1 - 1.21.0"},
452
about: "database master only (not actually possible in reality)",
453
fromVersion: "1.20.0",
455
targets: targets(upgrades.DatabaseMaster),
456
expectedSteps: []string{"state step 1 - 1.21.0", "step 1 - 1.21.0"},
459
about: "all state steps are run first",
460
fromVersion: "1.20.0",
462
targets: targets(upgrades.DatabaseMaster, upgrades.Controller),
463
expectedSteps: []string{
464
"state step 1 - 1.21.0", "state step 2 - 1.21.0",
465
"state step 1 - 1.22.0", "state step 2 - 1.22.0",
467
"step 1 - 1.22.0", "step 2 - 1.22.0",
471
about: "machine with multiple targets - each step only run once",
472
fromVersion: "1.20.0",
474
targets: targets(upgrades.HostMachine, upgrades.Controller),
475
expectedSteps: []string{"state step 2 - 1.21.0", "step 1 - 1.21.0"},
478
about: "step with multiple targets",
479
fromVersion: "1.21.0",
481
targets: targets(upgrades.HostMachine),
482
expectedSteps: []string{"step 1 - 1.22.0", "step 2 - 1.22.0"},
485
about: "machine and step with multiple targets - each step only run once",
486
fromVersion: "1.21.0",
488
targets: targets(upgrades.HostMachine, upgrades.Controller),
489
expectedSteps: []string{"state step 2 - 1.22.0", "step 1 - 1.22.0", "step 2 - 1.22.0"},
492
about: "upgrade to alpha release runs steps for final release",
493
fromVersion: "1.20.0",
494
toVersion: "1.21-alpha1",
495
targets: targets(upgrades.HostMachine),
496
expectedSteps: []string{"step 1 - 1.21.0"},
499
about: "upgrade to beta release runs steps for final release",
500
fromVersion: "1.20.0",
501
toVersion: "1.21-beta2",
502
targets: targets(upgrades.HostMachine),
503
expectedSteps: []string{"step 1 - 1.21.0"},
506
about: "starting release steps included when upgrading from an alpha release",
507
fromVersion: "1.20-alpha3",
509
targets: targets(upgrades.HostMachine),
510
expectedSteps: []string{"step 1 - 1.20.0", "step 2 - 1.20.0", "step 1 - 1.21.0"},
513
about: "starting release steps included when upgrading from an beta release",
514
fromVersion: "1.20-beta1",
516
targets: targets(upgrades.HostMachine),
517
expectedSteps: []string{"step 1 - 1.20.0", "step 2 - 1.20.0", "step 1 - 1.21.0"},
520
about: "nothing happens when the version hasn't changed but contains a tag",
521
fromVersion: "1.21-alpha1",
522
toVersion: "1.21-alpha1",
523
targets: targets(upgrades.DatabaseMaster),
524
expectedSteps: []string{},
527
about: "upgrades between pre-final versions should run steps for the final version",
528
fromVersion: "1.21-beta2",
529
toVersion: "1.21-beta3",
530
targets: targets(upgrades.DatabaseMaster),
531
expectedSteps: []string{"state step 1 - 1.21.0", "step 1 - 1.21.0"},
535
func (s *upgradeSuite) TestPerformUpgrade(c *gc.C) {
536
s.PatchValue(upgrades.StateUpgradeOperations, stateUpgradeOperations)
537
s.PatchValue(upgrades.UpgradeOperations, upgradeOperations)
538
for i, test := range upgradeTests {
539
c.Logf("%d: %s", i, test.about)
540
var messages []string
544
fromVersion := version.Zero
545
if test.fromVersion != "" {
546
fromVersion = version.MustParse(test.fromVersion)
548
toVersion := version.MustParse("1.18.0")
549
if test.toVersion != "" {
550
toVersion = version.MustParse(test.toVersion)
552
s.PatchValue(&jujuversion.Current, toVersion)
553
err := upgrades.PerformUpgrade(fromVersion, test.targets, ctx)
555
c.Check(err, jc.ErrorIsNil)
557
c.Check(err, gc.ErrorMatches, test.err)
559
c.Check(ctx.messages, jc.DeepEquals, test.expectedSteps)
563
type contextStep struct {
567
func (s *contextStep) Description() string {
571
func (s *contextStep) Targets() []upgrades.Target {
572
return []upgrades.Target{upgrades.Controller}
575
func (s *contextStep) Run(context upgrades.Context) error {
584
func (s *upgradeSuite) TestStateStepsGetRestrictedContext(c *gc.C) {
585
s.PatchValue(upgrades.StateUpgradeOperations, func() []upgrades.Operation {
586
return []upgrades.Operation{
587
&mockUpgradeOperation{
588
targetVersion: version.MustParse("1.21.0"),
589
steps: []upgrades.Step{&contextStep{useAPI: true}},
594
s.PatchValue(upgrades.UpgradeOperations,
595
func() []upgrades.Operation { return nil })
597
s.checkContextRestriction(c, "API not available from this context")
600
func (s *upgradeSuite) TestApiStepsGetRestrictedContext(c *gc.C) {
601
s.PatchValue(upgrades.StateUpgradeOperations,
602
func() []upgrades.Operation { return nil })
604
s.PatchValue(upgrades.UpgradeOperations, func() []upgrades.Operation {
605
return []upgrades.Operation{
606
&mockUpgradeOperation{
607
targetVersion: version.MustParse("1.21.0"),
608
steps: []upgrades.Step{&contextStep{useAPI: false}},
613
s.checkContextRestriction(c, "State not available from this context")
616
func (s *upgradeSuite) checkContextRestriction(c *gc.C, expectedPanic string) {
617
fromVersion := version.MustParse("1.20.0")
618
type fakeAgentConfigSetter struct{ agent.ConfigSetter }
619
ctx := upgrades.NewContext(fakeAgentConfigSetter{}, nil, new(state.State))
621
func() { upgrades.PerformUpgrade(fromVersion, targets(upgrades.Controller), ctx) },
622
gc.PanicMatches, expectedPanic,
626
func (s *upgradeSuite) TestStateStepsNotAttemptedWhenNoStateTarget(c *gc.C) {
628
stateUpgradeOperations := func() []upgrades.Operation {
632
s.PatchValue(upgrades.StateUpgradeOperations, stateUpgradeOperations)
635
upgradeOperations := func() []upgrades.Operation {
639
s.PatchValue(upgrades.UpgradeOperations, upgradeOperations)
641
fromVers := version.MustParse("1.18.0")
642
ctx := new(mockContext)
643
check := func(target upgrades.Target, expectedStateCallCount int) {
646
err := upgrades.PerformUpgrade(fromVers, targets(target), ctx)
647
c.Assert(err, jc.ErrorIsNil)
648
c.Assert(stateCount, gc.Equals, expectedStateCallCount)
649
c.Assert(apiCount, gc.Equals, 1)
652
check(upgrades.Controller, 1)
653
check(upgrades.DatabaseMaster, 1)
654
check(upgrades.AllMachines, 0)
655
check(upgrades.HostMachine, 0)
658
func (s *upgradeSuite) TestUpgradeOperationsOrdered(c *gc.C) {
659
var previous version.Number
660
for i, utv := range (*upgrades.UpgradeOperations)() {
661
vers := utv.TargetVersion()
663
c.Check(previous.Compare(vers), gc.Equals, -1)
669
func (s *upgradeSuite) TestStateUpgradeOperationsVersions(c *gc.C) {
670
versions := extractUpgradeVersions(c, (*upgrades.StateUpgradeOperations)())
671
c.Assert(versions, gc.DeepEquals, []string{
676
func (s *upgradeSuite) TestUpgradeOperationsVersions(c *gc.C) {
677
versions := extractUpgradeVersions(c, (*upgrades.UpgradeOperations)())
678
c.Assert(versions, gc.DeepEquals, []string{
683
func extractUpgradeVersions(c *gc.C, ops []upgrades.Operation) []string {
684
var versions []string
685
for _, utv := range ops {
686
vers := utv.TargetVersion()
687
// Upgrade steps should only be targeted at final versions (not alpha/beta).
688
c.Check(vers.Tag, gc.Equals, "placeholder")
689
versions = append(versions, vers.String())