1
// Copyright 2012, 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
14
jc "github.com/juju/testing/checkers"
15
"github.com/juju/utils/arch"
16
"github.com/juju/utils/series"
17
"github.com/juju/version"
18
gc "gopkg.in/check.v1"
20
"github.com/juju/juju/apiserver/common"
21
"github.com/juju/juju/apiserver/params"
22
apiservertesting "github.com/juju/juju/apiserver/testing"
23
cmdcommon "github.com/juju/juju/cmd/juju/common"
24
"github.com/juju/juju/cmd/modelcmd"
25
"github.com/juju/juju/environs/filestorage"
26
"github.com/juju/juju/environs/sync"
27
envtesting "github.com/juju/juju/environs/testing"
28
"github.com/juju/juju/environs/tools"
29
toolstesting "github.com/juju/juju/environs/tools/testing"
30
jujutesting "github.com/juju/juju/juju/testing"
31
"github.com/juju/juju/network"
32
"github.com/juju/juju/provider/dummy"
33
"github.com/juju/juju/state"
34
coretesting "github.com/juju/juju/testing"
35
coretools "github.com/juju/juju/tools"
36
jujuversion "github.com/juju/juju/version"
39
type UpgradeJujuSuite struct {
40
jujutesting.JujuConnSuite
42
resources *common.Resources
43
authoriser apiservertesting.FakeAuthorizer
46
cmdcommon.CmdBlockHelper
49
func (s *UpgradeJujuSuite) SetUpTest(c *gc.C) {
50
s.JujuConnSuite.SetUpTest(c)
51
s.resources = common.NewResources()
52
s.authoriser = apiservertesting.FakeAuthorizer{
53
Tag: s.AdminUserTag(c),
56
s.CmdBlockHelper = cmdcommon.NewCmdBlockHelper(s.APIState)
57
c.Assert(s.CmdBlockHelper, gc.NotNil)
58
s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
61
var _ = gc.Suite(&UpgradeJujuSuite{})
63
var upgradeJujuTests = []struct {
73
expectUploaded []string
74
upgradeMap map[int]version.Number
76
about: "unwanted extra argument",
77
currentVersion: "1.0.0-quantal-amd64",
78
args: []string{"foo"},
79
expectInitErr: "unrecognized args:.*",
81
about: "removed arg --dev specified",
82
currentVersion: "1.0.0-quantal-amd64",
83
args: []string{"--dev"},
84
expectInitErr: "flag provided but not defined: --dev",
86
about: "invalid --version value",
87
currentVersion: "1.0.0-quantal-amd64",
88
args: []string{"--version", "invalid-version"},
89
expectInitErr: "invalid version .*",
91
about: "just major version, no minor specified",
92
currentVersion: "4.2.0-quantal-amd64",
93
args: []string{"--version", "4"},
94
expectInitErr: `invalid version "4"`,
96
about: "major version upgrade to incompatible version",
97
currentVersion: "2.0.0-quantal-amd64",
98
agentVersion: "2.0.0",
99
args: []string{"--version", "5.2.0"},
100
expectErr: `unknown version "5.2.0"`,
102
about: "major version downgrade to incompatible version",
103
currentVersion: "4.2.0-quantal-amd64",
104
agentVersion: "4.2.0",
105
args: []string{"--version", "3.2.0"},
106
expectErr: "cannot change version from 4.2.0 to 3.2.0",
108
about: "--upload-tools with inappropriate version 1",
109
currentVersion: "4.2.0-quantal-amd64",
110
agentVersion: "4.2.0",
111
args: []string{"--upload-tools", "--version", "3.1.0"},
112
expectErr: "cannot change version from 4.2.0 to 3.1.0",
114
about: "--upload-tools with inappropriate version 2",
115
currentVersion: "3.2.7-quantal-amd64",
116
args: []string{"--upload-tools", "--version", "3.2.8.4"},
117
expectInitErr: "cannot specify build number when uploading tools",
119
about: "latest supported stable release",
120
tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64"},
121
currentVersion: "2.0.0-quantal-amd64",
122
agentVersion: "2.0.0",
123
expectVersion: "2.1.3",
125
about: "latest current release",
126
tools: []string{"2.0.5-quantal-amd64", "2.0.1-quantal-i386", "2.3.3-quantal-amd64"},
127
currentVersion: "2.0.0-quantal-amd64",
128
agentVersion: "2.0.0",
129
expectVersion: "2.0.5",
131
about: "latest current release matching CLI, major version, no matching major tools",
132
tools: []string{"2.8.2-quantal-amd64"},
133
currentVersion: "3.0.2-quantal-amd64",
134
agentVersion: "2.8.2",
135
expectVersion: "2.8.2",
137
about: "latest current release matching CLI, major version, no matching tools",
138
tools: []string{"3.3.0-quantal-amd64"},
139
currentVersion: "3.0.2-quantal-amd64",
140
agentVersion: "2.8.2",
141
expectVersion: "2.8.2",
143
about: "no next supported available",
144
tools: []string{"2.2.0-quantal-amd64", "2.2.5-quantal-i386", "2.3.3-quantal-amd64", "2.1-dev1-quantal-amd64"},
145
currentVersion: "2.0.0-quantal-amd64",
146
agentVersion: "2.0.0",
147
expectErr: "no more recent supported versions available",
149
about: "latest supported stable, when client is dev",
150
tools: []string{"2.1-dev1-quantal-amd64", "2.1.0-quantal-amd64", "2.3-dev0-quantal-amd64", "3.0.1-quantal-amd64"},
151
currentVersion: "2.1-dev0-quantal-amd64",
152
agentVersion: "2.0.0",
153
expectVersion: "2.1.0",
155
about: "latest current, when agent is dev",
156
tools: []string{"2.1-dev1-quantal-amd64", "2.2.0-quantal-amd64", "2.3-dev0-quantal-amd64", "3.0.1-quantal-amd64"},
157
currentVersion: "2.0.0-quantal-amd64",
158
agentVersion: "2.1-dev0",
159
expectVersion: "2.2.0",
161
about: "specified version",
162
tools: []string{"2.3-dev0-quantal-amd64"},
163
currentVersion: "2.0.0-quantal-amd64",
164
agentVersion: "2.0.0",
165
args: []string{"--version", "2.3-dev0"},
166
expectVersion: "2.3-dev0",
168
about: "specified major version",
169
tools: []string{"3.0.2-quantal-amd64"},
170
currentVersion: "3.0.2-quantal-amd64",
171
agentVersion: "2.8.2",
172
args: []string{"--version", "3.0.2"},
173
expectVersion: "3.0.2",
174
upgradeMap: map[int]version.Number{3: version.MustParse("2.8.2")},
176
about: "specified version missing, but already set",
177
currentVersion: "3.0.0-quantal-amd64",
178
agentVersion: "3.0.0",
179
args: []string{"--version", "3.0.0"},
180
expectVersion: "3.0.0",
182
about: "specified version, no tools",
183
currentVersion: "3.0.0-quantal-amd64",
184
agentVersion: "3.0.0",
185
args: []string{"--version", "3.2.0"},
186
expectErr: "no tools available",
188
about: "specified version, no matching major version",
189
tools: []string{"4.2.0-quantal-amd64"},
190
currentVersion: "3.0.0-quantal-amd64",
191
agentVersion: "3.0.0",
192
args: []string{"--version", "3.2.0"},
193
expectErr: "no matching tools available",
195
about: "specified version, no matching minor version",
196
tools: []string{"3.4.0-quantal-amd64"},
197
currentVersion: "3.0.0-quantal-amd64",
198
agentVersion: "3.0.0",
199
args: []string{"--version", "3.2.0"},
200
expectErr: "no matching tools available",
202
about: "specified version, no matching patch version",
203
tools: []string{"3.2.5-quantal-amd64"},
204
currentVersion: "3.0.0-quantal-amd64",
205
agentVersion: "3.0.0",
206
args: []string{"--version", "3.2.0"},
207
expectErr: "no matching tools available",
209
about: "specified version, no matching build version",
210
tools: []string{"3.2.0.2-quantal-amd64"},
211
currentVersion: "3.0.0-quantal-amd64",
212
agentVersion: "3.0.0",
213
args: []string{"--version", "3.2.0"},
214
expectErr: "no matching tools available",
216
about: "incompatible version (minor != 0)",
217
tools: []string{"3.2.0-quantal-amd64"},
218
currentVersion: "4.2.0-quantal-amd64",
219
agentVersion: "3.2.0",
220
args: []string{"--version", "3.2.0"},
221
expectErr: "cannot upgrade a 3.2.0 model with a 4.2.0 client",
223
about: "incompatible version (model major > client major)",
224
tools: []string{"3.2.0-quantal-amd64"},
225
currentVersion: "3.2.0-quantal-amd64",
226
agentVersion: "4.2.0",
227
args: []string{"--version", "3.2.0"},
228
expectErr: "cannot upgrade a 4.2.0 model with a 3.2.0 client",
230
about: "incompatible version (model major < client major - 1)",
231
tools: []string{"3.2.0-quantal-amd64"},
232
currentVersion: "4.0.2-quantal-amd64",
233
agentVersion: "2.0.0",
234
args: []string{"--version", "3.2.0"},
235
expectErr: "cannot upgrade a 2.0.0 model with a 4.0.2 client",
237
about: "minor version downgrade to incompatible version",
238
tools: []string{"3.2.0-quantal-amd64"},
239
currentVersion: "3.2.0-quantal-amd64",
240
agentVersion: "3.3-dev0",
241
args: []string{"--version", "3.2.0"},
242
expectErr: "cannot change version from 3.3-dev0 to 3.2.0",
244
about: "nothing available",
245
currentVersion: "2.0.0-quantal-amd64",
246
agentVersion: "2.0.0",
247
expectVersion: "2.0.0",
249
about: "nothing available 2",
250
currentVersion: "2.0.0-quantal-amd64",
251
tools: []string{"3.2.0-quantal-amd64"},
252
agentVersion: "2.0.0",
253
expectVersion: "2.0.0",
255
about: "upload with default series",
256
currentVersion: "2.2.0-quantal-amd64",
257
agentVersion: "2.0.0",
258
args: []string{"--upload-tools"},
259
expectVersion: "2.2.0.1",
260
expectUploaded: []string{"2.2.0.1-quantal-amd64", "2.2.0.1-%LTS%-amd64", "2.2.0.1-raring-amd64"},
262
about: "upload with explicit version",
263
currentVersion: "2.2.0-quantal-amd64",
264
agentVersion: "2.0.0",
265
args: []string{"--upload-tools", "--version", "2.7.3"},
266
expectVersion: "2.7.3.1",
267
expectUploaded: []string{"2.7.3.1-quantal-amd64", "2.7.3.1-%LTS%-amd64", "2.7.3.1-raring-amd64"},
269
about: "upload dev version, currently on release version",
270
currentVersion: "2.1.0-quantal-amd64",
271
agentVersion: "2.0.0",
272
args: []string{"--upload-tools"},
273
expectVersion: "2.1.0.1",
274
expectUploaded: []string{"2.1.0.1-quantal-amd64", "2.1.0.1-%LTS%-amd64", "2.1.0.1-raring-amd64"},
276
about: "upload bumps version when necessary",
277
tools: []string{"2.4.6-quantal-amd64", "2.4.8-quantal-amd64"},
278
currentVersion: "2.4.6-quantal-amd64",
279
agentVersion: "2.4.0",
280
args: []string{"--upload-tools"},
281
expectVersion: "2.4.6.1",
282
expectUploaded: []string{"2.4.6.1-quantal-amd64", "2.4.6.1-%LTS%-amd64", "2.4.6.1-raring-amd64"},
284
about: "upload re-bumps version when necessary",
285
tools: []string{"2.4.6-quantal-amd64", "2.4.6.2-saucy-i386", "2.4.8-quantal-amd64"},
286
currentVersion: "2.4.6-quantal-amd64",
287
agentVersion: "2.4.6.2",
288
args: []string{"--upload-tools"},
289
expectVersion: "2.4.6.3",
290
expectUploaded: []string{"2.4.6.3-quantal-amd64", "2.4.6.3-%LTS%-amd64", "2.4.6.3-raring-amd64"},
292
about: "upload with explicit version bumps when necessary",
293
currentVersion: "2.2.0-quantal-amd64",
294
tools: []string{"2.7.3.1-quantal-amd64"},
295
agentVersion: "2.0.0",
296
args: []string{"--upload-tools", "--version", "2.7.3"},
297
expectVersion: "2.7.3.2",
298
expectUploaded: []string{"2.7.3.2-quantal-amd64", "2.7.3.2-%LTS%-amd64", "2.7.3.2-raring-amd64"},
300
about: "latest supported stable release",
301
tools: []string{"1.21.3-quantal-amd64", "1.22.1-quantal-amd64"},
302
currentVersion: "1.22.1-quantal-amd64",
303
agentVersion: "1.20.14",
304
expectVersion: "1.21.3",
307
func (s *UpgradeJujuSuite) TestUpgradeJuju(c *gc.C) {
308
for i, test := range upgradeJujuTests {
309
c.Logf("\ntest %d: %s", i, test.about)
311
tools.DefaultBaseURL = ""
313
// Set up apparent CLI version and initialize the command.
314
current := version.MustParseBinary(test.currentVersion)
315
s.PatchValue(&jujuversion.Current, current.Number)
316
s.PatchValue(&arch.HostArch, func() string { return current.Arch })
317
s.PatchValue(&series.HostSeries, func() string { return current.Series })
318
com := newUpgradeJujuCommand(test.upgradeMap)
319
if err := coretesting.InitCommand(com, test.args); err != nil {
320
if test.expectInitErr != "" {
321
c.Check(err, gc.ErrorMatches, test.expectInitErr)
323
c.Check(err, jc.ErrorIsNil)
328
// Set up state and environ, and run the command.
329
toolsDir := c.MkDir()
330
updateAttrs := map[string]interface{}{
331
"agent-version": test.agentVersion,
332
"agent-metadata-url": "file://" + toolsDir + "/tools",
334
err := s.State.UpdateModelConfig(updateAttrs, nil, nil)
335
c.Assert(err, jc.ErrorIsNil)
336
versions := make([]version.Binary, len(test.tools))
337
for i, v := range test.tools {
338
versions[i] = version.MustParseBinary(v)
340
if len(versions) > 0 {
341
stor, err := filestorage.NewFileStorageWriter(toolsDir)
342
c.Assert(err, jc.ErrorIsNil)
343
envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...)
346
err = com.Run(coretesting.Context(c))
347
if test.expectErr != "" {
348
c.Check(err, gc.ErrorMatches, test.expectErr)
350
} else if !c.Check(err, jc.ErrorIsNil) {
354
// Check expected changes to environ/state.
355
cfg, err := s.State.ModelConfig()
356
c.Check(err, jc.ErrorIsNil)
357
agentVersion, ok := cfg.AgentVersion()
358
c.Check(ok, jc.IsTrue)
359
c.Check(agentVersion, gc.Equals, version.MustParse(test.expectVersion))
361
for _, uploaded := range test.expectUploaded {
362
// Substitute latest LTS for placeholder in expected series for uploaded tools
363
uploaded = strings.Replace(uploaded, "%LTS%", series.LatestLts(), 1)
364
vers := version.MustParseBinary(uploaded)
365
s.checkToolsUploaded(c, vers, agentVersion)
370
func (s *UpgradeJujuSuite) checkToolsUploaded(c *gc.C, vers version.Binary, agentVersion version.Number) {
371
storage, err := s.State.ToolsStorage()
372
c.Assert(err, jc.ErrorIsNil)
373
defer storage.Close()
374
_, r, err := storage.Open(vers.String())
375
if !c.Check(err, jc.ErrorIsNil) {
378
data, err := ioutil.ReadAll(r)
380
c.Check(err, jc.ErrorIsNil)
381
expectContent := version.Binary{
382
Number: agentVersion,
383
Arch: arch.HostArch(),
384
Series: series.HostSeries(),
386
checkToolsContent(c, data, "jujud contents "+expectContent.String())
389
func checkToolsContent(c *gc.C, data []byte, uploaded string) {
390
zr, err := gzip.NewReader(bytes.NewReader(data))
391
c.Check(err, jc.ErrorIsNil)
393
tr := tar.NewReader(zr)
396
hdr, err := tr.Next()
400
c.Check(err, jc.ErrorIsNil)
401
if strings.ContainsAny(hdr.Name, "/\\") {
404
if hdr.Typeflag != tar.TypeReg {
407
content, err := ioutil.ReadAll(tr)
408
c.Check(err, jc.ErrorIsNil)
409
c.Check(string(content), gc.Equals, uploaded)
412
c.Check(found, jc.IsTrue)
415
// JujuConnSuite very helpfully uploads some default
416
// tools to the environment's storage. We don't want
417
// 'em there; but we do want a consistent default-series
418
// in the environment state.
419
func (s *UpgradeJujuSuite) Reset(c *gc.C) {
420
s.JujuConnSuite.Reset(c)
421
envtesting.RemoveTools(c, s.DefaultToolsStorage, s.Environ.Config().AgentStream())
422
updateAttrs := map[string]interface{}{
423
"default-series": "raring",
424
"agent-version": "1.2.3",
426
err := s.State.UpdateModelConfig(updateAttrs, nil, nil)
427
c.Assert(err, jc.ErrorIsNil)
428
s.PatchValue(&sync.BuildToolsTarball, toolstesting.GetMockBuildTools(c))
430
// Set API host ports so FindTools works.
431
hostPorts := [][]network.HostPort{
432
network.NewHostPorts(1234, "0.1.2.3"),
434
err = s.State.SetAPIHostPorts(hostPorts)
435
c.Assert(err, jc.ErrorIsNil)
437
s.CmdBlockHelper = cmdcommon.NewCmdBlockHelper(s.APIState)
438
c.Assert(s.CmdBlockHelper, gc.NotNil)
439
s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
442
func (s *UpgradeJujuSuite) TestUpgradeJujuWithRealUpload(c *gc.C) {
444
s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99"))
445
cmd := newUpgradeJujuCommand(map[int]version.Number{2: version.MustParse("1.99.99")})
446
_, err := coretesting.RunCommand(c, cmd, "--upload-tools")
447
c.Assert(err, jc.ErrorIsNil)
448
vers := version.Binary{
449
Number: jujuversion.Current,
450
Arch: arch.HostArch(),
451
Series: series.HostSeries(),
454
s.checkToolsUploaded(c, vers, vers.Number)
457
func (s *UpgradeJujuSuite) TestBlockUpgradeJujuWithRealUpload(c *gc.C) {
459
s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99"))
460
cmd := newUpgradeJujuCommand(map[int]version.Number{2: version.MustParse("1.99.99")})
462
s.BlockAllChanges(c, "TestBlockUpgradeJujuWithRealUpload")
463
_, err := coretesting.RunCommand(c, cmd, "--upload-tools")
464
s.AssertBlocked(c, err, ".*TestBlockUpgradeJujuWithRealUpload.*")
467
func (s *UpgradeJujuSuite) TestFailUploadOnNonController(c *gc.C) {
468
fakeAPI := &fakeUpgradeJujuAPINoState{
470
uuid: "deadbeef-0000-400d-8000-4b1d0d06f00d",
471
controllerUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d",
473
s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) {
476
s.PatchValue(&getModelConfigAPI, func(*upgradeJujuCommand) (modelConfigAPI, error) {
479
cmd := newUpgradeJujuCommand(nil)
480
_, err := coretesting.RunCommand(c, cmd, "--upload-tools", "-m", "dummy-model")
481
c.Assert(err, gc.ErrorMatches, "--upload-tools can only be used with the controller model")
484
type DryRunTest struct {
488
currentVersion string
490
expectedCmdOutput string
493
func (s *UpgradeJujuSuite) TestUpgradeDryRun(c *gc.C) {
494
tests := []DryRunTest{
496
about: "dry run outputs and doesn't change anything when uploading tools",
497
cmdArgs: []string{"--upload-tools", "--dry-run"},
498
tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64", "2.2.3-quantal-amd64"},
499
currentVersion: "2.1.3-quantal-amd64",
500
agentVersion: "2.0.0",
501
expectedCmdOutput: `available tools:
505
upgrade to this version by running
506
juju upgrade-juju --version="2.1.3"
510
about: "dry run outputs and doesn't change anything",
511
cmdArgs: []string{"--dry-run"},
512
tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64", "2.2.3-quantal-amd64"},
513
currentVersion: "2.0.0-quantal-amd64",
514
agentVersion: "2.0.0",
515
expectedCmdOutput: `available tools:
516
2.1-dev1-quantal-amd64
523
upgrade to this version by running
524
juju upgrade-juju --version="2.1.3"
528
about: "dry run ignores unknown series",
529
cmdArgs: []string{"--dry-run"},
530
tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "1.2.3-myawesomeseries-amd64"},
531
currentVersion: "2.0.0-quantal-amd64",
532
agentVersion: "2.0.0",
533
expectedCmdOutput: `available tools:
539
upgrade to this version by running
540
juju upgrade-juju --version="2.1.3"
545
for i, test := range tests {
546
c.Logf("\ntest %d: %s", i, test.about)
548
tools.DefaultBaseURL = ""
550
s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools)
552
com := newUpgradeJujuCommand(nil)
553
err := coretesting.InitCommand(com, test.cmdArgs)
554
c.Assert(err, jc.ErrorIsNil)
556
ctx := coretesting.Context(c)
558
c.Assert(err, jc.ErrorIsNil)
560
// Check agent version doesn't change
561
cfg, err := s.State.ModelConfig()
562
c.Assert(err, jc.ErrorIsNil)
563
agentVer, ok := cfg.AgentVersion()
564
c.Assert(ok, jc.IsTrue)
565
c.Assert(agentVer, gc.Equals, version.MustParse(test.agentVersion))
566
output := coretesting.Stderr(ctx)
567
c.Assert(output, gc.Equals, test.expectedCmdOutput)
571
func (s *UpgradeJujuSuite) setUpEnvAndTools(c *gc.C, currentVersion string, agentVersion string, tools []string) {
572
current := version.MustParseBinary(currentVersion)
573
s.PatchValue(&jujuversion.Current, current.Number)
574
s.PatchValue(&arch.HostArch, func() string { return current.Arch })
575
s.PatchValue(&series.HostSeries, func() string { return current.Series })
577
toolsDir := c.MkDir()
578
updateAttrs := map[string]interface{}{
579
"agent-version": agentVersion,
580
"agent-metadata-url": "file://" + toolsDir + "/tools",
583
err := s.State.UpdateModelConfig(updateAttrs, nil, nil)
584
c.Assert(err, jc.ErrorIsNil)
585
versions := make([]version.Binary, len(tools))
586
for i, v := range tools {
587
versions[i], err = version.ParseBinary(v)
589
c.Assert(err, jc.Satisfies, series.IsUnknownOSForSeriesError)
592
if len(versions) > 0 {
593
stor, err := filestorage.NewFileStorageWriter(toolsDir)
594
c.Assert(err, jc.ErrorIsNil)
595
envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...)
599
func (s *UpgradeJujuSuite) TestUpgradesDifferentMajor(c *gc.C) {
600
toolsList49Only := `available tools:
609
currentVersion string
611
expectedVersion string
612
expectedCmdOutput string
613
expectedLogOutput string
614
excludedLogOutput string
616
upgradeMap map[int]version.Number
618
about: "upgrade previous major to latest previous major",
619
tools: []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"},
620
currentVersion: "5.0.0-trusty-amd64",
621
agentVersion: "4.8.5",
622
expectedVersion: "4.9.0",
623
expectedCmdOutput: toolsList49Only,
624
expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.0\).*started upgrade to 4.9.0.*`,
626
about: "upgrade previous major to latest previous major --dry-run still warns",
627
tools: []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"},
628
currentVersion: "5.0.1-trusty-amd64",
629
agentVersion: "4.8.5",
630
expectedVersion: "4.9.0",
631
expectedCmdOutput: toolsList49Only,
632
expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.1\).*started upgrade to 4.9.0.*`,
634
about: "upgrade previous major to latest previous major with --version",
635
cmdArgs: []string{"--version=4.9.0"},
636
tools: []string{"5.0.2-trusty-amd64", "4.9.0-trusty-amd64", "4.8.0-trusty-amd64"},
637
currentVersion: "5.0.2-trusty-amd64",
638
agentVersion: "4.7.5",
639
expectedVersion: "4.9.0",
640
expectedCmdOutput: toolsList49Only,
641
expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.2\).*started upgrade to 4.9.0.*`,
643
about: "can upgrade lower major version to current major version at minimum level",
644
cmdArgs: []string{"--version=6.0.5"},
645
tools: []string{"6.0.5-trusty-amd64", "5.9.9-trusty-amd64"},
646
currentVersion: "6.0.0-trusty-amd64",
647
agentVersion: "5.9.8",
648
expectedVersion: "6.0.5",
649
excludedLogOutput: `incompatible with this client (6.0.0)`,
650
upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")},
652
about: "can upgrade lower major version to current major version above minimum level",
653
cmdArgs: []string{"--version=6.0.5"},
654
tools: []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"},
655
currentVersion: "6.0.1-trusty-amd64",
656
agentVersion: "5.10.8",
657
expectedVersion: "6.0.5",
658
excludedLogOutput: `incompatible with this client (6.0.1)`,
659
upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")},
661
about: "can upgrade current to next major version",
662
cmdArgs: []string{"--version=6.0.5"},
663
tools: []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"},
664
currentVersion: "5.10.8-trusty-amd64",
665
agentVersion: "5.10.8",
666
expectedVersion: "6.0.5",
667
upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")},
669
about: "upgrade fails if not at minimum version",
670
cmdArgs: []string{"--version=7.0.1"},
671
tools: []string{"7.0.1-trusty-amd64"},
672
currentVersion: "7.0.1-trusty-amd64",
673
agentVersion: "6.0.0",
674
expectedVersion: "6.0.0",
675
expectedCmdOutput: "upgrades to a new major version must first go through 6.7.8\n",
676
expectedErr: "unable to upgrade to requested version",
677
upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")},
679
about: "upgrade fails if not a minor of 0",
680
cmdArgs: []string{"--version=7.1.1"},
681
tools: []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"},
682
currentVersion: "7.0.1-trusty-amd64",
683
agentVersion: "6.7.8",
684
expectedVersion: "6.7.8",
685
expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n",
686
expectedErr: "unable to upgrade to requested version",
687
upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")},
689
about: "upgrade fails if not at minimum version and not a minor of 0",
690
cmdArgs: []string{"--version=7.1.1"},
691
tools: []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"},
692
currentVersion: "7.0.1-trusty-amd64",
693
agentVersion: "6.0.0",
694
expectedVersion: "6.0.0",
695
expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n" +
696
"upgrades to a new major version must first go through 6.7.8\n",
697
expectedErr: "unable to upgrade to requested version",
698
upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")},
700
for i, test := range tests {
701
c.Logf("\ntest %d: %s", i, test.about)
703
tools.DefaultBaseURL = ""
705
s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools)
707
com := newUpgradeJujuCommand(test.upgradeMap)
708
err := coretesting.InitCommand(com, test.cmdArgs)
709
c.Assert(err, jc.ErrorIsNil)
711
ctx := coretesting.Context(c)
713
if test.expectedErr != "" {
714
c.Check(err, gc.ErrorMatches, test.expectedErr)
715
} else if !c.Check(err, jc.ErrorIsNil) {
719
// Check agent version doesn't change
720
cfg, err := s.State.ModelConfig()
721
c.Assert(err, jc.ErrorIsNil)
722
agentVer, ok := cfg.AgentVersion()
723
c.Assert(ok, jc.IsTrue)
724
c.Check(agentVer, gc.Equals, version.MustParse(test.expectedVersion))
725
output := coretesting.Stderr(ctx)
726
if test.expectedCmdOutput != "" {
727
c.Check(output, gc.Equals, test.expectedCmdOutput)
729
if test.expectedLogOutput != "" {
730
c.Check(strings.Replace(c.GetTestLog(), "\n", " ", -1), gc.Matches, test.expectedLogOutput)
732
if test.excludedLogOutput != "" {
733
c.Check(c.GetTestLog(), gc.Not(jc.Contains), test.excludedLogOutput)
738
func (s *UpgradeJujuSuite) TestUpgradeUnknownSeriesInStreams(c *gc.C) {
739
fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
740
fakeAPI.addTools("2.1.0-weird-amd64")
743
cmd := &upgradeJujuCommand{}
744
err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{})
745
c.Assert(err, jc.ErrorIsNil)
747
err = modelcmd.Wrap(cmd).Run(coretesting.Context(c))
748
c.Assert(err, gc.IsNil)
750
// ensure find tools was called
751
c.Assert(fakeAPI.findToolsCalled, jc.IsTrue)
752
c.Assert(fakeAPI.tools, gc.DeepEquals, []string{"2.1.0-weird-amd64", fakeAPI.nextVersion.String()})
755
func (s *UpgradeJujuSuite) TestUpgradeInProgress(c *gc.C) {
756
fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
757
fakeAPI.setVersionErr = ¶ms.Error{
758
Message: "a message from the server about the problem",
759
Code: params.CodeUpgradeInProgress,
762
cmd := &upgradeJujuCommand{}
763
err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{})
764
c.Assert(err, jc.ErrorIsNil)
766
err = modelcmd.Wrap(cmd).Run(coretesting.Context(c))
767
c.Assert(err, gc.ErrorMatches, "a message from the server about the problem\n"+
769
"Please wait for the upgrade to complete or if there was a problem with\n"+
770
"the last upgrade that has been resolved, consider running the\n"+
771
"upgrade-juju command with the --reset-previous-upgrade flag.",
775
func (s *UpgradeJujuSuite) TestBlockUpgradeInProgress(c *gc.C) {
776
fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
777
fakeAPI.setVersionErr = common.OperationBlockedError("the operation has been blocked")
779
cmd := &upgradeJujuCommand{}
780
err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{})
781
c.Assert(err, jc.ErrorIsNil)
784
s.BlockAllChanges(c, "TestBlockUpgradeInProgress")
785
err = modelcmd.Wrap(cmd).Run(coretesting.Context(c))
786
s.AssertBlocked(c, err, ".*To unblock changes.*")
789
func (s *UpgradeJujuSuite) TestResetPreviousUpgrade(c *gc.C) {
790
fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
793
ctx := coretesting.Context(c)
794
var stdin bytes.Buffer
797
run := func(answer string, expect bool, args ...string) {
800
stdin.WriteString(answer)
805
cmd := &upgradeJujuCommand{}
806
err := coretesting.InitCommand(modelcmd.Wrap(cmd),
807
append([]string{"--reset-previous-upgrade"}, args...))
808
c.Assert(err, jc.ErrorIsNil)
809
err = modelcmd.Wrap(cmd).Run(ctx)
811
c.Assert(err, jc.ErrorIsNil)
813
c.Assert(err, gc.ErrorMatches, "previous upgrade not reset and no new upgrade triggered")
816
c.Assert(fakeAPI.abortCurrentUpgradeCalled, gc.Equals, expect)
817
expectedVersion := version.Number{}
819
expectedVersion = fakeAPI.nextVersion.Number
821
c.Assert(fakeAPI.setVersionCalledWith, gc.Equals, expectedVersion)
824
const expectUpgrade = true
825
const expectNoUpgrade = false
827
// EOF on stdin - equivalent to answering no.
828
run("", expectNoUpgrade)
830
// -y on command line - no confirmation required
831
run("", expectUpgrade, "-y")
833
// --yes on command line - no confirmation required
834
run("", expectUpgrade, "--yes")
836
// various ways of saying "yes" to the prompt
837
for _, answer := range []string{"y", "Y", "yes", "YES"} {
838
run(answer, expectUpgrade)
841
// various ways of saying "no" to the prompt
842
for _, answer := range []string{"n", "N", "no", "foo"} {
843
run(answer, expectNoUpgrade)
847
func NewFakeUpgradeJujuAPI(c *gc.C, st *state.State) *fakeUpgradeJujuAPI {
848
nextVersion := version.Binary{
849
Number: jujuversion.Current,
850
Arch: arch.HostArch(),
851
Series: series.HostSeries(),
854
return &fakeUpgradeJujuAPI{
857
nextVersion: nextVersion,
861
type fakeUpgradeJujuAPI struct {
864
nextVersion version.Binary
866
abortCurrentUpgradeCalled bool
867
setVersionCalledWith version.Number
872
func (a *fakeUpgradeJujuAPI) reset() {
873
a.setVersionErr = nil
874
a.abortCurrentUpgradeCalled = false
875
a.setVersionCalledWith = version.Number{}
877
a.findToolsCalled = false
880
func (a *fakeUpgradeJujuAPI) patch(s *UpgradeJujuSuite) {
881
s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) {
884
s.PatchValue(&getModelConfigAPI, func(*upgradeJujuCommand) (modelConfigAPI, error) {
889
func (a *fakeUpgradeJujuAPI) addTools(tools ...string) {
890
for _, tool := range tools {
891
a.tools = append(a.tools, tool)
895
func (a *fakeUpgradeJujuAPI) ModelGet() (map[string]interface{}, error) {
896
config, err := a.st.ModelConfig()
898
return make(map[string]interface{}), err
900
return config.AllAttrs(), nil
903
func (a *fakeUpgradeJujuAPI) FindTools(majorVersion, minorVersion int, series, arch string) (
904
result params.FindToolsResult, err error,
906
a.findToolsCalled = true
907
a.tools = append(a.tools, a.nextVersion.String())
908
tools := toolstesting.MakeTools(a.c, a.c.MkDir(), "released", a.tools)
909
return params.FindToolsResult{
915
func (a *fakeUpgradeJujuAPI) UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (coretools.List, error) {
916
panic("not implemented")
919
func (a *fakeUpgradeJujuAPI) AbortCurrentUpgrade() error {
920
a.abortCurrentUpgradeCalled = true
924
func (a *fakeUpgradeJujuAPI) SetModelAgentVersion(v version.Number) error {
925
a.setVersionCalledWith = v
926
return a.setVersionErr
929
func (a *fakeUpgradeJujuAPI) Close() error {
933
// Mock an API with no state
934
type fakeUpgradeJujuAPINoState struct {
938
controllerUUID string
941
func (a *fakeUpgradeJujuAPINoState) Close() error {
945
func (a *fakeUpgradeJujuAPINoState) ModelGet() (map[string]interface{}, error) {
946
return dummy.SampleConfig().Merge(map[string]interface{}{
949
"controller-uuid": a.controllerUUID,