github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/config" 26 "github.com/juju/juju/environs/filestorage" 27 "github.com/juju/juju/environs/sync" 28 envtesting "github.com/juju/juju/environs/testing" 29 "github.com/juju/juju/environs/tools" 30 toolstesting "github.com/juju/juju/environs/tools/testing" 31 jujutesting "github.com/juju/juju/juju/testing" 32 "github.com/juju/juju/network" 33 _ "github.com/juju/juju/provider/dummy" 34 "github.com/juju/juju/state" 35 coretesting "github.com/juju/juju/testing" 36 coretools "github.com/juju/juju/tools" 37 jujuversion "github.com/juju/juju/version" 38 ) 39 40 type UpgradeJujuSuite struct { 41 jujutesting.JujuConnSuite 42 43 resources *common.Resources 44 authoriser apiservertesting.FakeAuthorizer 45 46 toolsDir string 47 cmdcommon.CmdBlockHelper 48 } 49 50 func (s *UpgradeJujuSuite) SetUpTest(c *gc.C) { 51 s.JujuConnSuite.SetUpTest(c) 52 s.resources = common.NewResources() 53 s.authoriser = apiservertesting.FakeAuthorizer{ 54 Tag: s.AdminUserTag(c), 55 } 56 57 s.CmdBlockHelper = cmdcommon.NewCmdBlockHelper(s.APIState) 58 c.Assert(s.CmdBlockHelper, gc.NotNil) 59 s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() }) 60 } 61 62 var _ = gc.Suite(&UpgradeJujuSuite{}) 63 64 var upgradeJujuTests = []struct { 65 about string 66 tools []string 67 currentVersion string 68 agentVersion string 69 70 args []string 71 expectInitErr string 72 expectErr string 73 expectVersion string 74 expectUploaded []string 75 upgradeMap map[int]version.Number 76 }{{ 77 about: "unwanted extra argument", 78 currentVersion: "1.0.0-quantal-amd64", 79 args: []string{"foo"}, 80 expectInitErr: "unrecognized args:.*", 81 }, { 82 about: "removed arg --dev specified", 83 currentVersion: "1.0.0-quantal-amd64", 84 args: []string{"--dev"}, 85 expectInitErr: "flag provided but not defined: --dev", 86 }, { 87 about: "invalid --version value", 88 currentVersion: "1.0.0-quantal-amd64", 89 args: []string{"--version", "invalid-version"}, 90 expectInitErr: "invalid version .*", 91 }, { 92 about: "just major version, no minor specified", 93 currentVersion: "4.2.0-quantal-amd64", 94 args: []string{"--version", "4"}, 95 expectInitErr: `invalid version "4"`, 96 }, { 97 about: "major version upgrade to incompatible version", 98 currentVersion: "2.0.0-quantal-amd64", 99 agentVersion: "2.0.0", 100 args: []string{"--version", "5.2.0"}, 101 expectErr: `unknown version "5.2.0"`, 102 }, { 103 about: "major version downgrade to incompatible version", 104 currentVersion: "4.2.0-quantal-amd64", 105 agentVersion: "4.2.0", 106 args: []string{"--version", "3.2.0"}, 107 expectErr: "cannot change version from 4.2.0 to 3.2.0", 108 }, { 109 about: "--upload-tools with inappropriate version 1", 110 currentVersion: "4.2.0-quantal-amd64", 111 agentVersion: "4.2.0", 112 args: []string{"--upload-tools", "--version", "3.1.0"}, 113 expectErr: "cannot change version from 4.2.0 to 3.1.0", 114 }, { 115 about: "--upload-tools with inappropriate version 2", 116 currentVersion: "3.2.7-quantal-amd64", 117 args: []string{"--upload-tools", "--version", "3.2.8.4"}, 118 expectInitErr: "cannot specify build number when uploading tools", 119 }, { 120 about: "latest supported stable release", 121 tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64"}, 122 currentVersion: "2.0.0-quantal-amd64", 123 agentVersion: "2.0.0", 124 expectVersion: "2.1.3", 125 }, { 126 about: "latest current release", 127 tools: []string{"2.0.5-quantal-amd64", "2.0.1-quantal-i386", "2.3.3-quantal-amd64"}, 128 currentVersion: "2.0.0-quantal-amd64", 129 agentVersion: "2.0.0", 130 expectVersion: "2.0.5", 131 }, { 132 about: "latest current release matching CLI, major version, no matching major tools", 133 tools: []string{"2.8.2-quantal-amd64"}, 134 currentVersion: "3.0.2-quantal-amd64", 135 agentVersion: "2.8.2", 136 expectVersion: "2.8.2", 137 }, { 138 about: "latest current release matching CLI, major version, no matching tools", 139 tools: []string{"3.3.0-quantal-amd64"}, 140 currentVersion: "3.0.2-quantal-amd64", 141 agentVersion: "2.8.2", 142 expectVersion: "2.8.2", 143 }, { 144 about: "no next supported available", 145 tools: []string{"2.2.0-quantal-amd64", "2.2.5-quantal-i386", "2.3.3-quantal-amd64", "2.1-dev1-quantal-amd64"}, 146 currentVersion: "2.0.0-quantal-amd64", 147 agentVersion: "2.0.0", 148 expectErr: "no more recent supported versions available", 149 }, { 150 about: "latest supported stable, when client is dev", 151 tools: []string{"2.1-dev1-quantal-amd64", "2.1.0-quantal-amd64", "2.3-dev0-quantal-amd64", "3.0.1-quantal-amd64"}, 152 currentVersion: "2.1-dev0-quantal-amd64", 153 agentVersion: "2.0.0", 154 expectVersion: "2.1.0", 155 }, { 156 about: "latest current, when agent is dev", 157 tools: []string{"2.1-dev1-quantal-amd64", "2.2.0-quantal-amd64", "2.3-dev0-quantal-amd64", "3.0.1-quantal-amd64"}, 158 currentVersion: "2.0.0-quantal-amd64", 159 agentVersion: "2.1-dev0", 160 expectVersion: "2.2.0", 161 }, { 162 about: "specified version", 163 tools: []string{"2.3-dev0-quantal-amd64"}, 164 currentVersion: "2.0.0-quantal-amd64", 165 agentVersion: "2.0.0", 166 args: []string{"--version", "2.3-dev0"}, 167 expectVersion: "2.3-dev0", 168 }, { 169 about: "specified major version", 170 tools: []string{"3.0.2-quantal-amd64"}, 171 currentVersion: "3.0.2-quantal-amd64", 172 agentVersion: "2.8.2", 173 args: []string{"--version", "3.0.2"}, 174 expectVersion: "3.0.2", 175 upgradeMap: map[int]version.Number{3: version.MustParse("2.8.2")}, 176 }, { 177 about: "specified version missing, but already set", 178 currentVersion: "3.0.0-quantal-amd64", 179 agentVersion: "3.0.0", 180 args: []string{"--version", "3.0.0"}, 181 expectVersion: "3.0.0", 182 }, { 183 about: "specified version, no tools", 184 currentVersion: "3.0.0-quantal-amd64", 185 agentVersion: "3.0.0", 186 args: []string{"--version", "3.2.0"}, 187 expectErr: "no tools available", 188 }, { 189 about: "specified version, no matching major version", 190 tools: []string{"4.2.0-quantal-amd64"}, 191 currentVersion: "3.0.0-quantal-amd64", 192 agentVersion: "3.0.0", 193 args: []string{"--version", "3.2.0"}, 194 expectErr: "no matching tools available", 195 }, { 196 about: "specified version, no matching minor version", 197 tools: []string{"3.4.0-quantal-amd64"}, 198 currentVersion: "3.0.0-quantal-amd64", 199 agentVersion: "3.0.0", 200 args: []string{"--version", "3.2.0"}, 201 expectErr: "no matching tools available", 202 }, { 203 about: "specified version, no matching patch version", 204 tools: []string{"3.2.5-quantal-amd64"}, 205 currentVersion: "3.0.0-quantal-amd64", 206 agentVersion: "3.0.0", 207 args: []string{"--version", "3.2.0"}, 208 expectErr: "no matching tools available", 209 }, { 210 about: "specified version, no matching build version", 211 tools: []string{"3.2.0.2-quantal-amd64"}, 212 currentVersion: "3.0.0-quantal-amd64", 213 agentVersion: "3.0.0", 214 args: []string{"--version", "3.2.0"}, 215 expectErr: "no matching tools available", 216 }, { 217 about: "incompatible version (minor != 0)", 218 tools: []string{"3.2.0-quantal-amd64"}, 219 currentVersion: "4.2.0-quantal-amd64", 220 agentVersion: "3.2.0", 221 args: []string{"--version", "3.2.0"}, 222 expectErr: "cannot upgrade a 3.2.0 model with a 4.2.0 client", 223 }, { 224 about: "incompatible version (model major > client major)", 225 tools: []string{"3.2.0-quantal-amd64"}, 226 currentVersion: "3.2.0-quantal-amd64", 227 agentVersion: "4.2.0", 228 args: []string{"--version", "3.2.0"}, 229 expectErr: "cannot upgrade a 4.2.0 model with a 3.2.0 client", 230 }, { 231 about: "incompatible version (model major < client major - 1)", 232 tools: []string{"3.2.0-quantal-amd64"}, 233 currentVersion: "4.0.2-quantal-amd64", 234 agentVersion: "2.0.0", 235 args: []string{"--version", "3.2.0"}, 236 expectErr: "cannot upgrade a 2.0.0 model with a 4.0.2 client", 237 }, { 238 about: "minor version downgrade to incompatible version", 239 tools: []string{"3.2.0-quantal-amd64"}, 240 currentVersion: "3.2.0-quantal-amd64", 241 agentVersion: "3.3-dev0", 242 args: []string{"--version", "3.2.0"}, 243 expectErr: "cannot change version from 3.3-dev0 to 3.2.0", 244 }, { 245 about: "nothing available", 246 currentVersion: "2.0.0-quantal-amd64", 247 agentVersion: "2.0.0", 248 expectVersion: "2.0.0", 249 }, { 250 about: "nothing available 2", 251 currentVersion: "2.0.0-quantal-amd64", 252 tools: []string{"3.2.0-quantal-amd64"}, 253 agentVersion: "2.0.0", 254 expectVersion: "2.0.0", 255 }, { 256 about: "upload with default series", 257 currentVersion: "2.2.0-quantal-amd64", 258 agentVersion: "2.0.0", 259 args: []string{"--upload-tools"}, 260 expectVersion: "2.2.0.1", 261 expectUploaded: []string{"2.2.0.1-quantal-amd64", "2.2.0.1-%LTS%-amd64", "2.2.0.1-raring-amd64"}, 262 }, { 263 about: "upload with explicit version", 264 currentVersion: "2.2.0-quantal-amd64", 265 agentVersion: "2.0.0", 266 args: []string{"--upload-tools", "--version", "2.7.3"}, 267 expectVersion: "2.7.3.1", 268 expectUploaded: []string{"2.7.3.1-quantal-amd64", "2.7.3.1-%LTS%-amd64", "2.7.3.1-raring-amd64"}, 269 }, { 270 about: "upload dev version, currently on release version", 271 currentVersion: "2.1.0-quantal-amd64", 272 agentVersion: "2.0.0", 273 args: []string{"--upload-tools"}, 274 expectVersion: "2.1.0.1", 275 expectUploaded: []string{"2.1.0.1-quantal-amd64", "2.1.0.1-%LTS%-amd64", "2.1.0.1-raring-amd64"}, 276 }, { 277 about: "upload bumps version when necessary", 278 tools: []string{"2.4.6-quantal-amd64", "2.4.8-quantal-amd64"}, 279 currentVersion: "2.4.6-quantal-amd64", 280 agentVersion: "2.4.0", 281 args: []string{"--upload-tools"}, 282 expectVersion: "2.4.6.1", 283 expectUploaded: []string{"2.4.6.1-quantal-amd64", "2.4.6.1-%LTS%-amd64", "2.4.6.1-raring-amd64"}, 284 }, { 285 about: "upload re-bumps version when necessary", 286 tools: []string{"2.4.6-quantal-amd64", "2.4.6.2-saucy-i386", "2.4.8-quantal-amd64"}, 287 currentVersion: "2.4.6-quantal-amd64", 288 agentVersion: "2.4.6.2", 289 args: []string{"--upload-tools"}, 290 expectVersion: "2.4.6.3", 291 expectUploaded: []string{"2.4.6.3-quantal-amd64", "2.4.6.3-%LTS%-amd64", "2.4.6.3-raring-amd64"}, 292 }, { 293 about: "upload with explicit version bumps when necessary", 294 currentVersion: "2.2.0-quantal-amd64", 295 tools: []string{"2.7.3.1-quantal-amd64"}, 296 agentVersion: "2.0.0", 297 args: []string{"--upload-tools", "--version", "2.7.3"}, 298 expectVersion: "2.7.3.2", 299 expectUploaded: []string{"2.7.3.2-quantal-amd64", "2.7.3.2-%LTS%-amd64", "2.7.3.2-raring-amd64"}, 300 }, { 301 about: "latest supported stable release", 302 tools: []string{"1.21.3-quantal-amd64", "1.22.1-quantal-amd64"}, 303 currentVersion: "1.22.1-quantal-amd64", 304 agentVersion: "1.20.14", 305 expectVersion: "1.21.3", 306 }} 307 308 func (s *UpgradeJujuSuite) TestUpgradeJuju(c *gc.C) { 309 for i, test := range upgradeJujuTests { 310 c.Logf("\ntest %d: %s", i, test.about) 311 s.Reset(c) 312 tools.DefaultBaseURL = "" 313 314 // Set up apparent CLI version and initialize the command. 315 current := version.MustParseBinary(test.currentVersion) 316 s.PatchValue(&jujuversion.Current, current.Number) 317 s.PatchValue(&arch.HostArch, func() string { return current.Arch }) 318 s.PatchValue(&series.HostSeries, func() string { return current.Series }) 319 com := newUpgradeJujuCommand(test.upgradeMap) 320 if err := coretesting.InitCommand(com, test.args); err != nil { 321 if test.expectInitErr != "" { 322 c.Check(err, gc.ErrorMatches, test.expectInitErr) 323 } else { 324 c.Check(err, jc.ErrorIsNil) 325 } 326 continue 327 } 328 329 // Set up state and environ, and run the command. 330 toolsDir := c.MkDir() 331 updateAttrs := map[string]interface{}{ 332 "agent-version": test.agentVersion, 333 "agent-metadata-url": "file://" + toolsDir + "/tools", 334 } 335 err := s.State.UpdateModelConfig(updateAttrs, nil, nil) 336 c.Assert(err, jc.ErrorIsNil) 337 versions := make([]version.Binary, len(test.tools)) 338 for i, v := range test.tools { 339 versions[i] = version.MustParseBinary(v) 340 } 341 if len(versions) > 0 { 342 stor, err := filestorage.NewFileStorageWriter(toolsDir) 343 c.Assert(err, jc.ErrorIsNil) 344 envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...) 345 } 346 347 err = com.Run(coretesting.Context(c)) 348 if test.expectErr != "" { 349 c.Check(err, gc.ErrorMatches, test.expectErr) 350 continue 351 } else if !c.Check(err, jc.ErrorIsNil) { 352 continue 353 } 354 355 // Check expected changes to environ/state. 356 cfg, err := s.State.ModelConfig() 357 c.Check(err, jc.ErrorIsNil) 358 agentVersion, ok := cfg.AgentVersion() 359 c.Check(ok, jc.IsTrue) 360 c.Check(agentVersion, gc.Equals, version.MustParse(test.expectVersion)) 361 362 for _, uploaded := range test.expectUploaded { 363 // Substitute latest LTS for placeholder in expected series for uploaded tools 364 uploaded = strings.Replace(uploaded, "%LTS%", config.LatestLtsSeries(), 1) 365 vers := version.MustParseBinary(uploaded) 366 s.checkToolsUploaded(c, vers, agentVersion) 367 } 368 } 369 } 370 371 func (s *UpgradeJujuSuite) checkToolsUploaded(c *gc.C, vers version.Binary, agentVersion version.Number) { 372 storage, err := s.State.ToolsStorage() 373 c.Assert(err, jc.ErrorIsNil) 374 defer storage.Close() 375 _, r, err := storage.Open(vers.String()) 376 if !c.Check(err, jc.ErrorIsNil) { 377 return 378 } 379 data, err := ioutil.ReadAll(r) 380 r.Close() 381 c.Check(err, jc.ErrorIsNil) 382 expectContent := version.Binary{ 383 Number: agentVersion, 384 Arch: arch.HostArch(), 385 Series: series.HostSeries(), 386 } 387 checkToolsContent(c, data, "jujud contents "+expectContent.String()) 388 } 389 390 func checkToolsContent(c *gc.C, data []byte, uploaded string) { 391 zr, err := gzip.NewReader(bytes.NewReader(data)) 392 c.Check(err, jc.ErrorIsNil) 393 defer zr.Close() 394 tr := tar.NewReader(zr) 395 found := false 396 for { 397 hdr, err := tr.Next() 398 if err == io.EOF { 399 break 400 } 401 c.Check(err, jc.ErrorIsNil) 402 if strings.ContainsAny(hdr.Name, "/\\") { 403 c.Fail() 404 } 405 if hdr.Typeflag != tar.TypeReg { 406 c.Fail() 407 } 408 content, err := ioutil.ReadAll(tr) 409 c.Check(err, jc.ErrorIsNil) 410 c.Check(string(content), gc.Equals, uploaded) 411 found = true 412 } 413 c.Check(found, jc.IsTrue) 414 } 415 416 // JujuConnSuite very helpfully uploads some default 417 // tools to the environment's storage. We don't want 418 // 'em there; but we do want a consistent default-series 419 // in the environment state. 420 func (s *UpgradeJujuSuite) Reset(c *gc.C) { 421 s.JujuConnSuite.Reset(c) 422 envtesting.RemoveTools(c, s.DefaultToolsStorage, s.Environ.Config().AgentStream()) 423 updateAttrs := map[string]interface{}{ 424 "default-series": "raring", 425 "agent-version": "1.2.3", 426 } 427 err := s.State.UpdateModelConfig(updateAttrs, nil, nil) 428 c.Assert(err, jc.ErrorIsNil) 429 s.PatchValue(&sync.BuildToolsTarball, toolstesting.GetMockBuildTools(c)) 430 431 // Set API host ports so FindTools works. 432 hostPorts := [][]network.HostPort{ 433 network.NewHostPorts(1234, "0.1.2.3"), 434 } 435 err = s.State.SetAPIHostPorts(hostPorts) 436 c.Assert(err, jc.ErrorIsNil) 437 438 s.CmdBlockHelper = cmdcommon.NewCmdBlockHelper(s.APIState) 439 c.Assert(s.CmdBlockHelper, gc.NotNil) 440 s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() }) 441 } 442 443 func (s *UpgradeJujuSuite) TestUpgradeJujuWithRealUpload(c *gc.C) { 444 s.Reset(c) 445 s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99")) 446 cmd := newUpgradeJujuCommand(map[int]version.Number{2: version.MustParse("1.99.99")}) 447 _, err := coretesting.RunCommand(c, cmd, "--upload-tools") 448 c.Assert(err, jc.ErrorIsNil) 449 vers := version.Binary{ 450 Number: jujuversion.Current, 451 Arch: arch.HostArch(), 452 Series: series.HostSeries(), 453 } 454 vers.Build = 1 455 s.checkToolsUploaded(c, vers, vers.Number) 456 } 457 458 func (s *UpgradeJujuSuite) TestBlockUpgradeJujuWithRealUpload(c *gc.C) { 459 s.Reset(c) 460 s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99")) 461 cmd := newUpgradeJujuCommand(map[int]version.Number{2: version.MustParse("1.99.99")}) 462 // Block operation 463 s.BlockAllChanges(c, "TestBlockUpgradeJujuWithRealUpload") 464 _, err := coretesting.RunCommand(c, cmd, "--upload-tools") 465 s.AssertBlocked(c, err, ".*TestBlockUpgradeJujuWithRealUpload.*") 466 } 467 468 type DryRunTest struct { 469 about string 470 cmdArgs []string 471 tools []string 472 currentVersion string 473 agentVersion string 474 expectedCmdOutput string 475 } 476 477 func (s *UpgradeJujuSuite) TestUpgradeDryRun(c *gc.C) { 478 tests := []DryRunTest{ 479 { 480 about: "dry run outputs and doesn't change anything when uploading tools", 481 cmdArgs: []string{"--upload-tools", "--dry-run"}, 482 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"}, 483 currentVersion: "2.1.3-quantal-amd64", 484 agentVersion: "2.0.0", 485 expectedCmdOutput: `available tools: 486 2.1.3-quantal-amd64 487 best version: 488 2.1.3 489 upgrade to this version by running 490 juju upgrade-juju --version="2.1.3" 491 `, 492 }, 493 { 494 about: "dry run outputs and doesn't change anything", 495 cmdArgs: []string{"--dry-run"}, 496 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"}, 497 currentVersion: "2.0.0-quantal-amd64", 498 agentVersion: "2.0.0", 499 expectedCmdOutput: `available tools: 500 2.1-dev1-quantal-amd64 501 2.1.0-quantal-amd64 502 2.1.2-quantal-i386 503 2.1.3-quantal-amd64 504 2.2.3-quantal-amd64 505 best version: 506 2.1.3 507 upgrade to this version by running 508 juju upgrade-juju --version="2.1.3" 509 `, 510 }, 511 { 512 about: "dry run ignores unknown series", 513 cmdArgs: []string{"--dry-run"}, 514 tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "1.2.3-myawesomeseries-amd64"}, 515 currentVersion: "2.0.0-quantal-amd64", 516 agentVersion: "2.0.0", 517 expectedCmdOutput: `available tools: 518 2.1.0-quantal-amd64 519 2.1.2-quantal-i386 520 2.1.3-quantal-amd64 521 best version: 522 2.1.3 523 upgrade to this version by running 524 juju upgrade-juju --version="2.1.3" 525 `, 526 }, 527 } 528 529 for i, test := range tests { 530 c.Logf("\ntest %d: %s", i, test.about) 531 s.Reset(c) 532 tools.DefaultBaseURL = "" 533 534 s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools) 535 536 com := newUpgradeJujuCommand(nil) 537 err := coretesting.InitCommand(com, test.cmdArgs) 538 c.Assert(err, jc.ErrorIsNil) 539 540 ctx := coretesting.Context(c) 541 err = com.Run(ctx) 542 c.Assert(err, jc.ErrorIsNil) 543 544 // Check agent version doesn't change 545 cfg, err := s.State.ModelConfig() 546 c.Assert(err, jc.ErrorIsNil) 547 agentVer, ok := cfg.AgentVersion() 548 c.Assert(ok, jc.IsTrue) 549 c.Assert(agentVer, gc.Equals, version.MustParse(test.agentVersion)) 550 output := coretesting.Stderr(ctx) 551 c.Assert(output, gc.Equals, test.expectedCmdOutput) 552 } 553 } 554 555 func (s *UpgradeJujuSuite) setUpEnvAndTools(c *gc.C, currentVersion string, agentVersion string, tools []string) { 556 current := version.MustParseBinary(currentVersion) 557 s.PatchValue(&jujuversion.Current, current.Number) 558 s.PatchValue(&arch.HostArch, func() string { return current.Arch }) 559 s.PatchValue(&series.HostSeries, func() string { return current.Series }) 560 561 toolsDir := c.MkDir() 562 updateAttrs := map[string]interface{}{ 563 "agent-version": agentVersion, 564 "agent-metadata-url": "file://" + toolsDir + "/tools", 565 } 566 567 err := s.State.UpdateModelConfig(updateAttrs, nil, nil) 568 c.Assert(err, jc.ErrorIsNil) 569 versions := make([]version.Binary, len(tools)) 570 for i, v := range tools { 571 versions[i], err = version.ParseBinary(v) 572 if err != nil { 573 c.Assert(err, jc.Satisfies, series.IsUnknownOSForSeriesError) 574 } 575 } 576 if len(versions) > 0 { 577 stor, err := filestorage.NewFileStorageWriter(toolsDir) 578 c.Assert(err, jc.ErrorIsNil) 579 envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...) 580 } 581 } 582 583 func (s *UpgradeJujuSuite) TestUpgradesDifferentMajor(c *gc.C) { 584 toolsList49Only := `available tools: 585 4.9.0-trusty-amd64 586 best version: 587 4.9.0 588 ` 589 tests := []struct { 590 about string 591 cmdArgs []string 592 tools []string 593 currentVersion string 594 agentVersion string 595 expectedVersion string 596 expectedCmdOutput string 597 expectedLogOutput string 598 excludedLogOutput string 599 expectedErr string 600 upgradeMap map[int]version.Number 601 }{{ 602 about: "upgrade previous major to latest previous major", 603 tools: []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"}, 604 currentVersion: "5.0.0-trusty-amd64", 605 agentVersion: "4.8.5", 606 expectedVersion: "4.9.0", 607 expectedCmdOutput: toolsList49Only, 608 expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.0\).*started upgrade to 4.9.0.*`, 609 }, { 610 about: "upgrade previous major to latest previous major --dry-run still warns", 611 tools: []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"}, 612 currentVersion: "5.0.1-trusty-amd64", 613 agentVersion: "4.8.5", 614 expectedVersion: "4.9.0", 615 expectedCmdOutput: toolsList49Only, 616 expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.1\).*started upgrade to 4.9.0.*`, 617 }, { 618 about: "upgrade previous major to latest previous major with --version", 619 cmdArgs: []string{"--version=4.9.0"}, 620 tools: []string{"5.0.2-trusty-amd64", "4.9.0-trusty-amd64", "4.8.0-trusty-amd64"}, 621 currentVersion: "5.0.2-trusty-amd64", 622 agentVersion: "4.7.5", 623 expectedVersion: "4.9.0", 624 expectedCmdOutput: toolsList49Only, 625 expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.2\).*started upgrade to 4.9.0.*`, 626 }, { 627 about: "can upgrade lower major version to current major version at minimum level", 628 cmdArgs: []string{"--version=6.0.5"}, 629 tools: []string{"6.0.5-trusty-amd64", "5.9.9-trusty-amd64"}, 630 currentVersion: "6.0.0-trusty-amd64", 631 agentVersion: "5.9.8", 632 expectedVersion: "6.0.5", 633 excludedLogOutput: `incompatible with this client (6.0.0)`, 634 upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")}, 635 }, { 636 about: "can upgrade lower major version to current major version above minimum level", 637 cmdArgs: []string{"--version=6.0.5"}, 638 tools: []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"}, 639 currentVersion: "6.0.1-trusty-amd64", 640 agentVersion: "5.10.8", 641 expectedVersion: "6.0.5", 642 excludedLogOutput: `incompatible with this client (6.0.1)`, 643 upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")}, 644 }, { 645 about: "can upgrade current to next major version", 646 cmdArgs: []string{"--version=6.0.5"}, 647 tools: []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"}, 648 currentVersion: "5.10.8-trusty-amd64", 649 agentVersion: "5.10.8", 650 expectedVersion: "6.0.5", 651 upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")}, 652 }, { 653 about: "upgrade fails if not at minimum version", 654 cmdArgs: []string{"--version=7.0.1"}, 655 tools: []string{"7.0.1-trusty-amd64"}, 656 currentVersion: "7.0.1-trusty-amd64", 657 agentVersion: "6.0.0", 658 expectedVersion: "6.0.0", 659 expectedCmdOutput: "upgrades to a new major version must first go through 6.7.8\n", 660 expectedErr: "unable to upgrade to requested version", 661 upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")}, 662 }, { 663 about: "upgrade fails if not a minor of 0", 664 cmdArgs: []string{"--version=7.1.1"}, 665 tools: []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"}, 666 currentVersion: "7.0.1-trusty-amd64", 667 agentVersion: "6.7.8", 668 expectedVersion: "6.7.8", 669 expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n", 670 expectedErr: "unable to upgrade to requested version", 671 upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")}, 672 }, { 673 about: "upgrade fails if not at minimum version and not a minor of 0", 674 cmdArgs: []string{"--version=7.1.1"}, 675 tools: []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"}, 676 currentVersion: "7.0.1-trusty-amd64", 677 agentVersion: "6.0.0", 678 expectedVersion: "6.0.0", 679 expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n" + 680 "upgrades to a new major version must first go through 6.7.8\n", 681 expectedErr: "unable to upgrade to requested version", 682 upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")}, 683 }} 684 for i, test := range tests { 685 c.Logf("\ntest %d: %s", i, test.about) 686 s.Reset(c) 687 tools.DefaultBaseURL = "" 688 689 s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools) 690 691 com := newUpgradeJujuCommand(test.upgradeMap) 692 err := coretesting.InitCommand(com, test.cmdArgs) 693 c.Assert(err, jc.ErrorIsNil) 694 695 ctx := coretesting.Context(c) 696 err = com.Run(ctx) 697 if test.expectedErr != "" { 698 c.Check(err, gc.ErrorMatches, test.expectedErr) 699 } else if !c.Check(err, jc.ErrorIsNil) { 700 continue 701 } 702 703 // Check agent version doesn't change 704 cfg, err := s.State.ModelConfig() 705 c.Assert(err, jc.ErrorIsNil) 706 agentVer, ok := cfg.AgentVersion() 707 c.Assert(ok, jc.IsTrue) 708 c.Check(agentVer, gc.Equals, version.MustParse(test.expectedVersion)) 709 output := coretesting.Stderr(ctx) 710 if test.expectedCmdOutput != "" { 711 c.Check(output, gc.Equals, test.expectedCmdOutput) 712 } 713 if test.expectedLogOutput != "" { 714 c.Check(strings.Replace(c.GetTestLog(), "\n", " ", -1), gc.Matches, test.expectedLogOutput) 715 } 716 if test.excludedLogOutput != "" { 717 c.Check(c.GetTestLog(), gc.Not(jc.Contains), test.excludedLogOutput) 718 } 719 } 720 } 721 722 func (s *UpgradeJujuSuite) TestUpgradeUnknownSeriesInStreams(c *gc.C) { 723 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 724 fakeAPI.addTools("2.1.0-weird-amd64") 725 fakeAPI.patch(s) 726 727 cmd := &upgradeJujuCommand{} 728 err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{}) 729 c.Assert(err, jc.ErrorIsNil) 730 731 err = modelcmd.Wrap(cmd).Run(coretesting.Context(c)) 732 c.Assert(err, gc.IsNil) 733 734 // ensure find tools was called 735 c.Assert(fakeAPI.findToolsCalled, jc.IsTrue) 736 c.Assert(fakeAPI.tools, gc.DeepEquals, []string{"2.1.0-weird-amd64", fakeAPI.nextVersion.String()}) 737 } 738 739 func (s *UpgradeJujuSuite) TestUpgradeInProgress(c *gc.C) { 740 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 741 fakeAPI.setVersionErr = ¶ms.Error{ 742 Message: "a message from the server about the problem", 743 Code: params.CodeUpgradeInProgress, 744 } 745 fakeAPI.patch(s) 746 cmd := &upgradeJujuCommand{} 747 err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{}) 748 c.Assert(err, jc.ErrorIsNil) 749 750 err = modelcmd.Wrap(cmd).Run(coretesting.Context(c)) 751 c.Assert(err, gc.ErrorMatches, "a message from the server about the problem\n"+ 752 "\n"+ 753 "Please wait for the upgrade to complete or if there was a problem with\n"+ 754 "the last upgrade that has been resolved, consider running the\n"+ 755 "upgrade-juju command with the --reset-previous-upgrade flag.", 756 ) 757 } 758 759 func (s *UpgradeJujuSuite) TestBlockUpgradeInProgress(c *gc.C) { 760 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 761 fakeAPI.setVersionErr = common.OperationBlockedError("the operation has been blocked") 762 fakeAPI.patch(s) 763 cmd := &upgradeJujuCommand{} 764 err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{}) 765 c.Assert(err, jc.ErrorIsNil) 766 767 // Block operation 768 s.BlockAllChanges(c, "TestBlockUpgradeInProgress") 769 err = modelcmd.Wrap(cmd).Run(coretesting.Context(c)) 770 s.AssertBlocked(c, err, ".*To unblock changes.*") 771 } 772 773 func (s *UpgradeJujuSuite) TestResetPreviousUpgrade(c *gc.C) { 774 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 775 fakeAPI.patch(s) 776 777 ctx := coretesting.Context(c) 778 var stdin bytes.Buffer 779 ctx.Stdin = &stdin 780 781 run := func(answer string, expect bool, args ...string) { 782 stdin.Reset() 783 if answer != "" { 784 stdin.WriteString(answer) 785 } 786 787 fakeAPI.reset() 788 789 cmd := &upgradeJujuCommand{} 790 err := coretesting.InitCommand(modelcmd.Wrap(cmd), 791 append([]string{"--reset-previous-upgrade"}, args...)) 792 c.Assert(err, jc.ErrorIsNil) 793 err = modelcmd.Wrap(cmd).Run(ctx) 794 if expect { 795 c.Assert(err, jc.ErrorIsNil) 796 } else { 797 c.Assert(err, gc.ErrorMatches, "previous upgrade not reset and no new upgrade triggered") 798 } 799 800 c.Assert(fakeAPI.abortCurrentUpgradeCalled, gc.Equals, expect) 801 expectedVersion := version.Number{} 802 if expect { 803 expectedVersion = fakeAPI.nextVersion.Number 804 } 805 c.Assert(fakeAPI.setVersionCalledWith, gc.Equals, expectedVersion) 806 } 807 808 const expectUpgrade = true 809 const expectNoUpgrade = false 810 811 // EOF on stdin - equivalent to answering no. 812 run("", expectNoUpgrade) 813 814 // -y on command line - no confirmation required 815 run("", expectUpgrade, "-y") 816 817 // --yes on command line - no confirmation required 818 run("", expectUpgrade, "--yes") 819 820 // various ways of saying "yes" to the prompt 821 for _, answer := range []string{"y", "Y", "yes", "YES"} { 822 run(answer, expectUpgrade) 823 } 824 825 // various ways of saying "no" to the prompt 826 for _, answer := range []string{"n", "N", "no", "foo"} { 827 run(answer, expectNoUpgrade) 828 } 829 } 830 831 func NewFakeUpgradeJujuAPI(c *gc.C, st *state.State) *fakeUpgradeJujuAPI { 832 nextVersion := version.Binary{ 833 Number: jujuversion.Current, 834 Arch: arch.HostArch(), 835 Series: series.HostSeries(), 836 } 837 nextVersion.Minor++ 838 return &fakeUpgradeJujuAPI{ 839 c: c, 840 st: st, 841 nextVersion: nextVersion, 842 } 843 } 844 845 type fakeUpgradeJujuAPI struct { 846 c *gc.C 847 st *state.State 848 nextVersion version.Binary 849 setVersionErr error 850 abortCurrentUpgradeCalled bool 851 setVersionCalledWith version.Number 852 tools []string 853 findToolsCalled bool 854 } 855 856 func (a *fakeUpgradeJujuAPI) reset() { 857 a.setVersionErr = nil 858 a.abortCurrentUpgradeCalled = false 859 a.setVersionCalledWith = version.Number{} 860 a.tools = []string{} 861 a.findToolsCalled = false 862 } 863 864 func (a *fakeUpgradeJujuAPI) patch(s *UpgradeJujuSuite) { 865 s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) { 866 return a, nil 867 }) 868 } 869 870 func (a *fakeUpgradeJujuAPI) addTools(tools ...string) { 871 for _, tool := range tools { 872 a.tools = append(a.tools, tool) 873 } 874 } 875 876 func (a *fakeUpgradeJujuAPI) ModelGet() (map[string]interface{}, error) { 877 config, err := a.st.ModelConfig() 878 if err != nil { 879 return make(map[string]interface{}), err 880 } 881 return config.AllAttrs(), nil 882 } 883 884 func (a *fakeUpgradeJujuAPI) FindTools(majorVersion, minorVersion int, series, arch string) ( 885 result params.FindToolsResult, err error, 886 ) { 887 a.findToolsCalled = true 888 a.tools = append(a.tools, a.nextVersion.String()) 889 tools := toolstesting.MakeTools(a.c, a.c.MkDir(), "released", a.tools) 890 return params.FindToolsResult{ 891 List: tools, 892 Error: nil, 893 }, nil 894 } 895 896 func (a *fakeUpgradeJujuAPI) UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (coretools.List, error) { 897 panic("not implemented") 898 } 899 900 func (a *fakeUpgradeJujuAPI) AbortCurrentUpgrade() error { 901 a.abortCurrentUpgradeCalled = true 902 return nil 903 } 904 905 func (a *fakeUpgradeJujuAPI) SetModelAgentVersion(v version.Number) error { 906 a.setVersionCalledWith = v 907 return a.setVersionErr 908 } 909 910 func (a *fakeUpgradeJujuAPI) Close() error { 911 return nil 912 }