github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cmd/juju/commands/upgradejuju_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 "archive/tar" 8 "bytes" 9 "compress/gzip" 10 "io" 11 "io/ioutil" 12 "strings" 13 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" 19 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" 37 ) 38 39 type UpgradeJujuSuite struct { 40 jujutesting.JujuConnSuite 41 42 resources *common.Resources 43 authoriser apiservertesting.FakeAuthorizer 44 45 toolsDir string 46 cmdcommon.CmdBlockHelper 47 } 48 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), 54 } 55 56 s.CmdBlockHelper = cmdcommon.NewCmdBlockHelper(s.APIState) 57 c.Assert(s.CmdBlockHelper, gc.NotNil) 58 s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() }) 59 } 60 61 var _ = gc.Suite(&UpgradeJujuSuite{}) 62 63 var upgradeJujuTests = []struct { 64 about string 65 tools []string 66 currentVersion string 67 agentVersion string 68 69 args []string 70 expectInitErr string 71 expectErr string 72 expectVersion string 73 expectUploaded []string 74 upgradeMap map[int]version.Number 75 }{{ 76 about: "unwanted extra argument", 77 currentVersion: "1.0.0-quantal-amd64", 78 args: []string{"foo"}, 79 expectInitErr: "unrecognized args:.*", 80 }, { 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", 85 }, { 86 about: "invalid --version value", 87 currentVersion: "1.0.0-quantal-amd64", 88 args: []string{"--version", "invalid-version"}, 89 expectInitErr: "invalid version .*", 90 }, { 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"`, 95 }, { 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"`, 101 }, { 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", 107 }, { 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", 113 }, { 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", 118 }, { 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", 124 }, { 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", 130 }, { 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", 136 }, { 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", 142 }, { 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", 148 }, { 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", 154 }, { 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", 160 }, { 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", 167 }, { 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")}, 175 }, { 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", 181 }, { 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", 187 }, { 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", 194 }, { 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", 201 }, { 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", 208 }, { 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", 215 }, { 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", 222 }, { 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", 229 }, { 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", 236 }, { 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", 243 }, { 244 about: "nothing available", 245 currentVersion: "2.0.0-quantal-amd64", 246 agentVersion: "2.0.0", 247 expectVersion: "2.0.0", 248 }, { 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", 254 }, { 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"}, 261 }, { 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"}, 268 }, { 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"}, 275 }, { 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"}, 283 }, { 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"}, 291 }, { 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"}, 299 }, { 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", 305 }} 306 307 func (s *UpgradeJujuSuite) TestUpgradeJuju(c *gc.C) { 308 for i, test := range upgradeJujuTests { 309 c.Logf("\ntest %d: %s", i, test.about) 310 s.Reset(c) 311 tools.DefaultBaseURL = "" 312 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) 322 } else { 323 c.Check(err, jc.ErrorIsNil) 324 } 325 continue 326 } 327 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", 333 } 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) 339 } 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...) 344 } 345 346 err = com.Run(coretesting.Context(c)) 347 if test.expectErr != "" { 348 c.Check(err, gc.ErrorMatches, test.expectErr) 349 continue 350 } else if !c.Check(err, jc.ErrorIsNil) { 351 continue 352 } 353 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)) 360 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) 366 } 367 } 368 } 369 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) { 376 return 377 } 378 data, err := ioutil.ReadAll(r) 379 r.Close() 380 c.Check(err, jc.ErrorIsNil) 381 expectContent := version.Binary{ 382 Number: agentVersion, 383 Arch: arch.HostArch(), 384 Series: series.HostSeries(), 385 } 386 checkToolsContent(c, data, "jujud contents "+expectContent.String()) 387 } 388 389 func checkToolsContent(c *gc.C, data []byte, uploaded string) { 390 zr, err := gzip.NewReader(bytes.NewReader(data)) 391 c.Check(err, jc.ErrorIsNil) 392 defer zr.Close() 393 tr := tar.NewReader(zr) 394 found := false 395 for { 396 hdr, err := tr.Next() 397 if err == io.EOF { 398 break 399 } 400 c.Check(err, jc.ErrorIsNil) 401 if strings.ContainsAny(hdr.Name, "/\\") { 402 c.Fail() 403 } 404 if hdr.Typeflag != tar.TypeReg { 405 c.Fail() 406 } 407 content, err := ioutil.ReadAll(tr) 408 c.Check(err, jc.ErrorIsNil) 409 c.Check(string(content), gc.Equals, uploaded) 410 found = true 411 } 412 c.Check(found, jc.IsTrue) 413 } 414 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", 425 } 426 err := s.State.UpdateModelConfig(updateAttrs, nil, nil) 427 c.Assert(err, jc.ErrorIsNil) 428 s.PatchValue(&sync.BuildToolsTarball, toolstesting.GetMockBuildTools(c)) 429 430 // Set API host ports so FindTools works. 431 hostPorts := [][]network.HostPort{ 432 network.NewHostPorts(1234, "0.1.2.3"), 433 } 434 err = s.State.SetAPIHostPorts(hostPorts) 435 c.Assert(err, jc.ErrorIsNil) 436 437 s.CmdBlockHelper = cmdcommon.NewCmdBlockHelper(s.APIState) 438 c.Assert(s.CmdBlockHelper, gc.NotNil) 439 s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() }) 440 } 441 442 func (s *UpgradeJujuSuite) TestUpgradeJujuWithRealUpload(c *gc.C) { 443 s.Reset(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(), 452 } 453 vers.Build = 1 454 s.checkToolsUploaded(c, vers, vers.Number) 455 } 456 457 func (s *UpgradeJujuSuite) TestBlockUpgradeJujuWithRealUpload(c *gc.C) { 458 s.Reset(c) 459 s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99")) 460 cmd := newUpgradeJujuCommand(map[int]version.Number{2: version.MustParse("1.99.99")}) 461 // Block operation 462 s.BlockAllChanges(c, "TestBlockUpgradeJujuWithRealUpload") 463 _, err := coretesting.RunCommand(c, cmd, "--upload-tools") 464 s.AssertBlocked(c, err, ".*TestBlockUpgradeJujuWithRealUpload.*") 465 } 466 467 type DryRunTest struct { 468 about string 469 cmdArgs []string 470 tools []string 471 currentVersion string 472 agentVersion string 473 expectedCmdOutput string 474 } 475 476 func (s *UpgradeJujuSuite) TestUpgradeDryRun(c *gc.C) { 477 tests := []DryRunTest{ 478 { 479 about: "dry run outputs and doesn't change anything when uploading tools", 480 cmdArgs: []string{"--upload-tools", "--dry-run"}, 481 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"}, 482 currentVersion: "2.1.3-quantal-amd64", 483 agentVersion: "2.0.0", 484 expectedCmdOutput: `available tools: 485 2.1.3-quantal-amd64 486 best version: 487 2.1.3 488 upgrade to this version by running 489 juju upgrade-juju --version="2.1.3" 490 `, 491 }, 492 { 493 about: "dry run outputs and doesn't change anything", 494 cmdArgs: []string{"--dry-run"}, 495 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"}, 496 currentVersion: "2.0.0-quantal-amd64", 497 agentVersion: "2.0.0", 498 expectedCmdOutput: `available tools: 499 2.1-dev1-quantal-amd64 500 2.1.0-quantal-amd64 501 2.1.2-quantal-i386 502 2.1.3-quantal-amd64 503 2.2.3-quantal-amd64 504 best version: 505 2.1.3 506 upgrade to this version by running 507 juju upgrade-juju --version="2.1.3" 508 `, 509 }, 510 { 511 about: "dry run ignores unknown series", 512 cmdArgs: []string{"--dry-run"}, 513 tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "1.2.3-myawesomeseries-amd64"}, 514 currentVersion: "2.0.0-quantal-amd64", 515 agentVersion: "2.0.0", 516 expectedCmdOutput: `available tools: 517 2.1.0-quantal-amd64 518 2.1.2-quantal-i386 519 2.1.3-quantal-amd64 520 best version: 521 2.1.3 522 upgrade to this version by running 523 juju upgrade-juju --version="2.1.3" 524 `, 525 }, 526 } 527 528 for i, test := range tests { 529 c.Logf("\ntest %d: %s", i, test.about) 530 s.Reset(c) 531 tools.DefaultBaseURL = "" 532 533 s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools) 534 535 com := newUpgradeJujuCommand(nil) 536 err := coretesting.InitCommand(com, test.cmdArgs) 537 c.Assert(err, jc.ErrorIsNil) 538 539 ctx := coretesting.Context(c) 540 err = com.Run(ctx) 541 c.Assert(err, jc.ErrorIsNil) 542 543 // Check agent version doesn't change 544 cfg, err := s.State.ModelConfig() 545 c.Assert(err, jc.ErrorIsNil) 546 agentVer, ok := cfg.AgentVersion() 547 c.Assert(ok, jc.IsTrue) 548 c.Assert(agentVer, gc.Equals, version.MustParse(test.agentVersion)) 549 output := coretesting.Stderr(ctx) 550 c.Assert(output, gc.Equals, test.expectedCmdOutput) 551 } 552 } 553 554 func (s *UpgradeJujuSuite) setUpEnvAndTools(c *gc.C, currentVersion string, agentVersion string, tools []string) { 555 current := version.MustParseBinary(currentVersion) 556 s.PatchValue(&jujuversion.Current, current.Number) 557 s.PatchValue(&arch.HostArch, func() string { return current.Arch }) 558 s.PatchValue(&series.HostSeries, func() string { return current.Series }) 559 560 toolsDir := c.MkDir() 561 updateAttrs := map[string]interface{}{ 562 "agent-version": agentVersion, 563 "agent-metadata-url": "file://" + toolsDir + "/tools", 564 } 565 566 err := s.State.UpdateModelConfig(updateAttrs, nil, nil) 567 c.Assert(err, jc.ErrorIsNil) 568 versions := make([]version.Binary, len(tools)) 569 for i, v := range tools { 570 versions[i], err = version.ParseBinary(v) 571 if err != nil { 572 c.Assert(err, jc.Satisfies, series.IsUnknownOSForSeriesError) 573 } 574 } 575 if len(versions) > 0 { 576 stor, err := filestorage.NewFileStorageWriter(toolsDir) 577 c.Assert(err, jc.ErrorIsNil) 578 envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...) 579 } 580 } 581 582 func (s *UpgradeJujuSuite) TestUpgradesDifferentMajor(c *gc.C) { 583 toolsList49Only := `available tools: 584 4.9.0-trusty-amd64 585 best version: 586 4.9.0 587 ` 588 tests := []struct { 589 about string 590 cmdArgs []string 591 tools []string 592 currentVersion string 593 agentVersion string 594 expectedVersion string 595 expectedCmdOutput string 596 expectedLogOutput string 597 excludedLogOutput string 598 expectedErr string 599 upgradeMap map[int]version.Number 600 }{{ 601 about: "upgrade previous major to latest previous major", 602 tools: []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"}, 603 currentVersion: "5.0.0-trusty-amd64", 604 agentVersion: "4.8.5", 605 expectedVersion: "4.9.0", 606 expectedCmdOutput: toolsList49Only, 607 expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.0\).*started upgrade to 4.9.0.*`, 608 }, { 609 about: "upgrade previous major to latest previous major --dry-run still warns", 610 tools: []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"}, 611 currentVersion: "5.0.1-trusty-amd64", 612 agentVersion: "4.8.5", 613 expectedVersion: "4.9.0", 614 expectedCmdOutput: toolsList49Only, 615 expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.1\).*started upgrade to 4.9.0.*`, 616 }, { 617 about: "upgrade previous major to latest previous major with --version", 618 cmdArgs: []string{"--version=4.9.0"}, 619 tools: []string{"5.0.2-trusty-amd64", "4.9.0-trusty-amd64", "4.8.0-trusty-amd64"}, 620 currentVersion: "5.0.2-trusty-amd64", 621 agentVersion: "4.7.5", 622 expectedVersion: "4.9.0", 623 expectedCmdOutput: toolsList49Only, 624 expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.2\).*started upgrade to 4.9.0.*`, 625 }, { 626 about: "can upgrade lower major version to current major version at minimum level", 627 cmdArgs: []string{"--version=6.0.5"}, 628 tools: []string{"6.0.5-trusty-amd64", "5.9.9-trusty-amd64"}, 629 currentVersion: "6.0.0-trusty-amd64", 630 agentVersion: "5.9.8", 631 expectedVersion: "6.0.5", 632 excludedLogOutput: `incompatible with this client (6.0.0)`, 633 upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")}, 634 }, { 635 about: "can upgrade lower major version to current major version above minimum level", 636 cmdArgs: []string{"--version=6.0.5"}, 637 tools: []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"}, 638 currentVersion: "6.0.1-trusty-amd64", 639 agentVersion: "5.10.8", 640 expectedVersion: "6.0.5", 641 excludedLogOutput: `incompatible with this client (6.0.1)`, 642 upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")}, 643 }, { 644 about: "can upgrade current to next major version", 645 cmdArgs: []string{"--version=6.0.5"}, 646 tools: []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"}, 647 currentVersion: "5.10.8-trusty-amd64", 648 agentVersion: "5.10.8", 649 expectedVersion: "6.0.5", 650 upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")}, 651 }, { 652 about: "upgrade fails if not at minimum version", 653 cmdArgs: []string{"--version=7.0.1"}, 654 tools: []string{"7.0.1-trusty-amd64"}, 655 currentVersion: "7.0.1-trusty-amd64", 656 agentVersion: "6.0.0", 657 expectedVersion: "6.0.0", 658 expectedCmdOutput: "upgrades to a new major version must first go through 6.7.8\n", 659 expectedErr: "unable to upgrade to requested version", 660 upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")}, 661 }, { 662 about: "upgrade fails if not a minor of 0", 663 cmdArgs: []string{"--version=7.1.1"}, 664 tools: []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"}, 665 currentVersion: "7.0.1-trusty-amd64", 666 agentVersion: "6.7.8", 667 expectedVersion: "6.7.8", 668 expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n", 669 expectedErr: "unable to upgrade to requested version", 670 upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")}, 671 }, { 672 about: "upgrade fails if not at minimum version and not a minor of 0", 673 cmdArgs: []string{"--version=7.1.1"}, 674 tools: []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"}, 675 currentVersion: "7.0.1-trusty-amd64", 676 agentVersion: "6.0.0", 677 expectedVersion: "6.0.0", 678 expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n" + 679 "upgrades to a new major version must first go through 6.7.8\n", 680 expectedErr: "unable to upgrade to requested version", 681 upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")}, 682 }} 683 for i, test := range tests { 684 c.Logf("\ntest %d: %s", i, test.about) 685 s.Reset(c) 686 tools.DefaultBaseURL = "" 687 688 s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools) 689 690 com := newUpgradeJujuCommand(test.upgradeMap) 691 err := coretesting.InitCommand(com, test.cmdArgs) 692 c.Assert(err, jc.ErrorIsNil) 693 694 ctx := coretesting.Context(c) 695 err = com.Run(ctx) 696 if test.expectedErr != "" { 697 c.Check(err, gc.ErrorMatches, test.expectedErr) 698 } else if !c.Check(err, jc.ErrorIsNil) { 699 continue 700 } 701 702 // Check agent version doesn't change 703 cfg, err := s.State.ModelConfig() 704 c.Assert(err, jc.ErrorIsNil) 705 agentVer, ok := cfg.AgentVersion() 706 c.Assert(ok, jc.IsTrue) 707 c.Check(agentVer, gc.Equals, version.MustParse(test.expectedVersion)) 708 output := coretesting.Stderr(ctx) 709 if test.expectedCmdOutput != "" { 710 c.Check(output, gc.Equals, test.expectedCmdOutput) 711 } 712 if test.expectedLogOutput != "" { 713 c.Check(strings.Replace(c.GetTestLog(), "\n", " ", -1), gc.Matches, test.expectedLogOutput) 714 } 715 if test.excludedLogOutput != "" { 716 c.Check(c.GetTestLog(), gc.Not(jc.Contains), test.excludedLogOutput) 717 } 718 } 719 } 720 721 func (s *UpgradeJujuSuite) TestUpgradeUnknownSeriesInStreams(c *gc.C) { 722 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 723 fakeAPI.addTools("2.1.0-weird-amd64") 724 fakeAPI.patch(s) 725 726 cmd := &upgradeJujuCommand{} 727 err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{}) 728 c.Assert(err, jc.ErrorIsNil) 729 730 err = modelcmd.Wrap(cmd).Run(coretesting.Context(c)) 731 c.Assert(err, gc.IsNil) 732 733 // ensure find tools was called 734 c.Assert(fakeAPI.findToolsCalled, jc.IsTrue) 735 c.Assert(fakeAPI.tools, gc.DeepEquals, []string{"2.1.0-weird-amd64", fakeAPI.nextVersion.String()}) 736 } 737 738 func (s *UpgradeJujuSuite) TestUpgradeInProgress(c *gc.C) { 739 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 740 fakeAPI.setVersionErr = ¶ms.Error{ 741 Message: "a message from the server about the problem", 742 Code: params.CodeUpgradeInProgress, 743 } 744 fakeAPI.patch(s) 745 cmd := &upgradeJujuCommand{} 746 err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{}) 747 c.Assert(err, jc.ErrorIsNil) 748 749 err = modelcmd.Wrap(cmd).Run(coretesting.Context(c)) 750 c.Assert(err, gc.ErrorMatches, "a message from the server about the problem\n"+ 751 "\n"+ 752 "Please wait for the upgrade to complete or if there was a problem with\n"+ 753 "the last upgrade that has been resolved, consider running the\n"+ 754 "upgrade-juju command with the --reset-previous-upgrade flag.", 755 ) 756 } 757 758 func (s *UpgradeJujuSuite) TestBlockUpgradeInProgress(c *gc.C) { 759 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 760 fakeAPI.setVersionErr = common.OperationBlockedError("the operation has been blocked") 761 fakeAPI.patch(s) 762 cmd := &upgradeJujuCommand{} 763 err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{}) 764 c.Assert(err, jc.ErrorIsNil) 765 766 // Block operation 767 s.BlockAllChanges(c, "TestBlockUpgradeInProgress") 768 err = modelcmd.Wrap(cmd).Run(coretesting.Context(c)) 769 s.AssertBlocked(c, err, ".*To unblock changes.*") 770 } 771 772 func (s *UpgradeJujuSuite) TestResetPreviousUpgrade(c *gc.C) { 773 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 774 fakeAPI.patch(s) 775 776 ctx := coretesting.Context(c) 777 var stdin bytes.Buffer 778 ctx.Stdin = &stdin 779 780 run := func(answer string, expect bool, args ...string) { 781 stdin.Reset() 782 if answer != "" { 783 stdin.WriteString(answer) 784 } 785 786 fakeAPI.reset() 787 788 cmd := &upgradeJujuCommand{} 789 err := coretesting.InitCommand(modelcmd.Wrap(cmd), 790 append([]string{"--reset-previous-upgrade"}, args...)) 791 c.Assert(err, jc.ErrorIsNil) 792 err = modelcmd.Wrap(cmd).Run(ctx) 793 if expect { 794 c.Assert(err, jc.ErrorIsNil) 795 } else { 796 c.Assert(err, gc.ErrorMatches, "previous upgrade not reset and no new upgrade triggered") 797 } 798 799 c.Assert(fakeAPI.abortCurrentUpgradeCalled, gc.Equals, expect) 800 expectedVersion := version.Number{} 801 if expect { 802 expectedVersion = fakeAPI.nextVersion.Number 803 } 804 c.Assert(fakeAPI.setVersionCalledWith, gc.Equals, expectedVersion) 805 } 806 807 const expectUpgrade = true 808 const expectNoUpgrade = false 809 810 // EOF on stdin - equivalent to answering no. 811 run("", expectNoUpgrade) 812 813 // -y on command line - no confirmation required 814 run("", expectUpgrade, "-y") 815 816 // --yes on command line - no confirmation required 817 run("", expectUpgrade, "--yes") 818 819 // various ways of saying "yes" to the prompt 820 for _, answer := range []string{"y", "Y", "yes", "YES"} { 821 run(answer, expectUpgrade) 822 } 823 824 // various ways of saying "no" to the prompt 825 for _, answer := range []string{"n", "N", "no", "foo"} { 826 run(answer, expectNoUpgrade) 827 } 828 } 829 830 func NewFakeUpgradeJujuAPI(c *gc.C, st *state.State) *fakeUpgradeJujuAPI { 831 nextVersion := version.Binary{ 832 Number: jujuversion.Current, 833 Arch: arch.HostArch(), 834 Series: series.HostSeries(), 835 } 836 nextVersion.Minor++ 837 return &fakeUpgradeJujuAPI{ 838 c: c, 839 st: st, 840 nextVersion: nextVersion, 841 } 842 } 843 844 type fakeUpgradeJujuAPI struct { 845 c *gc.C 846 st *state.State 847 nextVersion version.Binary 848 setVersionErr error 849 abortCurrentUpgradeCalled bool 850 setVersionCalledWith version.Number 851 tools []string 852 findToolsCalled bool 853 } 854 855 func (a *fakeUpgradeJujuAPI) reset() { 856 a.setVersionErr = nil 857 a.abortCurrentUpgradeCalled = false 858 a.setVersionCalledWith = version.Number{} 859 a.tools = []string{} 860 a.findToolsCalled = false 861 } 862 863 func (a *fakeUpgradeJujuAPI) patch(s *UpgradeJujuSuite) { 864 s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) { 865 return a, nil 866 }) 867 } 868 869 func (a *fakeUpgradeJujuAPI) addTools(tools ...string) { 870 for _, tool := range tools { 871 a.tools = append(a.tools, tool) 872 } 873 } 874 875 func (a *fakeUpgradeJujuAPI) ModelGet() (map[string]interface{}, error) { 876 config, err := a.st.ModelConfig() 877 if err != nil { 878 return make(map[string]interface{}), err 879 } 880 return config.AllAttrs(), nil 881 } 882 883 func (a *fakeUpgradeJujuAPI) FindTools(majorVersion, minorVersion int, series, arch string) ( 884 result params.FindToolsResult, err error, 885 ) { 886 a.findToolsCalled = true 887 a.tools = append(a.tools, a.nextVersion.String()) 888 tools := toolstesting.MakeTools(a.c, a.c.MkDir(), "released", a.tools) 889 return params.FindToolsResult{ 890 List: tools, 891 Error: nil, 892 }, nil 893 } 894 895 func (a *fakeUpgradeJujuAPI) UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (coretools.List, error) { 896 panic("not implemented") 897 } 898 899 func (a *fakeUpgradeJujuAPI) AbortCurrentUpgrade() error { 900 a.abortCurrentUpgradeCalled = true 901 return nil 902 } 903 904 func (a *fakeUpgradeJujuAPI) SetModelAgentVersion(v version.Number) error { 905 a.setVersionCalledWith = v 906 return a.setVersionErr 907 } 908 909 func (a *fakeUpgradeJujuAPI) Close() error { 910 return nil 911 }