github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "github.com/juju/errors" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/utils/arch" 17 "github.com/juju/utils/series" 18 "github.com/juju/version" 19 gc "gopkg.in/check.v1" 20 21 "github.com/juju/juju/apiserver/common" 22 "github.com/juju/juju/apiserver/params" 23 apiservertesting "github.com/juju/juju/apiserver/testing" 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 coretesting.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 = coretesting.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 --agent-version value", 87 currentVersion: "1.0.0-quantal-amd64", 88 args: []string{"--agent-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{"--agent-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{"--agent-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{"--agent-version", "3.2.0"}, 106 expectErr: "cannot change version from 4.2.0 to 3.2.0", 107 }, { 108 about: "--build-agent with inappropriate version 1", 109 currentVersion: "4.2.0-quantal-amd64", 110 agentVersion: "4.2.0", 111 args: []string{"--build-agent", "--agent-version", "3.1.0"}, 112 expectErr: "cannot change version from 4.2.0 to 3.1.0", 113 }, { 114 about: "--build-agent with inappropriate version 2", 115 currentVersion: "3.2.7-quantal-amd64", 116 args: []string{"--build-agent", "--agent-version", "3.2.8.4"}, 117 expectInitErr: "cannot specify build number when building an agent", 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 expectErr: "no compatible tools available", 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-dev0.1", 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{"--agent-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{"--agent-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{"--agent-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{"--agent-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{"--agent-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{"--agent-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{"--agent-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{"--agent-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{"--agent-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{"--agent-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{"--agent-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{"--agent-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{"--build-agent"}, 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{"--build-agent", "--agent-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{"--build-agent"}, 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{"--build-agent"}, 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{"--build-agent"}, 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{"--build-agent", "--agent-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.22.1.1", 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.BuildAgentTarball, 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 = coretesting.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, "--build-agent") 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) TestUpgradeJujuWithImplicitUploadDevAgent(c *gc.C) { 458 s.Reset(c) 459 fakeAPI := &fakeUpgradeJujuAPINoState{ 460 name: "dummy-model", 461 uuid: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 462 controllerUUID: "deadbeef-1bad-500d-9000-4b1d0d06f00d", 463 agentVersion: "1.99.99.1", 464 } 465 s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) { 466 return fakeAPI, nil 467 }) 468 s.PatchValue(&getModelConfigAPI, func(*upgradeJujuCommand) (modelConfigAPI, error) { 469 return fakeAPI, nil 470 }) 471 s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99")) 472 cmd := newUpgradeJujuCommand(nil) 473 _, err := coretesting.RunCommand(c, cmd) 474 c.Assert(err, jc.ErrorIsNil) 475 c.Assert(fakeAPI.tools, gc.Not(gc.HasLen), 0) 476 c.Assert(fakeAPI.tools[0].Version.Number, gc.Equals, version.MustParse("1.99.99.1")) 477 } 478 479 func (s *UpgradeJujuSuite) TestUpgradeJujuWithImplicitUploadNewerClient(c *gc.C) { 480 s.Reset(c) 481 fakeAPI := &fakeUpgradeJujuAPINoState{ 482 name: "dummy-model", 483 uuid: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 484 controllerUUID: "deadbeef-1bad-500d-9000-4b1d0d06f00d", 485 agentVersion: "1.99.99", 486 } 487 s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) { 488 return fakeAPI, nil 489 }) 490 s.PatchValue(&getModelConfigAPI, func(*upgradeJujuCommand) (modelConfigAPI, error) { 491 return fakeAPI, nil 492 }) 493 s.PatchValue(&jujuversion.Current, version.MustParse("1.100.0")) 494 cmd := newUpgradeJujuCommand(nil) 495 _, err := coretesting.RunCommand(c, cmd) 496 c.Assert(err, jc.ErrorIsNil) 497 c.Assert(fakeAPI.tools, gc.Not(gc.HasLen), 0) 498 c.Assert(fakeAPI.tools[0].Version.Number, gc.Equals, version.MustParse("1.100.0.1")) 499 c.Assert(fakeAPI.modelAgentVersion, gc.Equals, fakeAPI.tools[0].Version.Number) 500 } 501 502 func (s *UpgradeJujuSuite) TestUpgradeJujuWithImplicitUploadNonController(c *gc.C) { 503 s.Reset(c) 504 fakeAPI := &fakeUpgradeJujuAPINoState{ 505 name: "dummy-model", 506 uuid: "deadbeef-0000-400d-8000-4b1d0d06f00d", 507 controllerUUID: "deadbeef-1bad-500d-9000-4b1d0d06f00d", 508 agentVersion: "1.99.99.1", 509 } 510 s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) { 511 return fakeAPI, nil 512 }) 513 s.PatchValue(&getModelConfigAPI, func(*upgradeJujuCommand) (modelConfigAPI, error) { 514 return fakeAPI, nil 515 }) 516 s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99")) 517 cmd := newUpgradeJujuCommand(nil) 518 _, err := coretesting.RunCommand(c, cmd) 519 c.Assert(err, gc.ErrorMatches, "no more recent supported versions available") 520 } 521 522 func (s *UpgradeJujuSuite) TestBlockUpgradeJujuWithRealUpload(c *gc.C) { 523 s.Reset(c) 524 s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99")) 525 cmd := newUpgradeJujuCommand(map[int]version.Number{2: version.MustParse("1.99.99")}) 526 // Block operation 527 s.BlockAllChanges(c, "TestBlockUpgradeJujuWithRealUpload") 528 _, err := coretesting.RunCommand(c, cmd, "--build-agent") 529 coretesting.AssertOperationWasBlocked(c, err, ".*TestBlockUpgradeJujuWithRealUpload.*") 530 } 531 532 func (s *UpgradeJujuSuite) TestFailUploadOnNonController(c *gc.C) { 533 fakeAPI := &fakeUpgradeJujuAPINoState{ 534 name: "dummy-model", 535 uuid: "deadbeef-0000-400d-8000-4b1d0d06f00d", 536 controllerUUID: "deadbeef-1bad-500d-9000-4b1d0d06f00d", 537 agentVersion: "1.99.99", 538 } 539 s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) { 540 return fakeAPI, nil 541 }) 542 s.PatchValue(&getModelConfigAPI, func(*upgradeJujuCommand) (modelConfigAPI, error) { 543 return fakeAPI, nil 544 }) 545 cmd := newUpgradeJujuCommand(nil) 546 _, err := coretesting.RunCommand(c, cmd, "--build-agent", "-m", "dummy-model") 547 c.Assert(err, gc.ErrorMatches, "--build-agent can only be used with the controller model") 548 } 549 550 type DryRunTest struct { 551 about string 552 cmdArgs []string 553 tools []string 554 currentVersion string 555 agentVersion string 556 expectedCmdOutput string 557 } 558 559 func (s *UpgradeJujuSuite) TestUpgradeDryRun(c *gc.C) { 560 tests := []DryRunTest{ 561 { 562 about: "dry run outputs and doesn't change anything when uploading tools", 563 cmdArgs: []string{"--build-agent", "--dry-run"}, 564 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"}, 565 currentVersion: "2.1.3-quantal-amd64", 566 agentVersion: "2.0.0", 567 expectedCmdOutput: `upgrade to this version by running 568 juju upgrade-juju --agent-version="2.1.3" 569 `, 570 }, 571 { 572 about: "dry run outputs and doesn't change anything", 573 cmdArgs: []string{"--dry-run"}, 574 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"}, 575 currentVersion: "2.0.0-quantal-amd64", 576 agentVersion: "2.0.0", 577 expectedCmdOutput: `upgrade to this version by running 578 juju upgrade-juju --agent-version="2.1.3" 579 `, 580 }, 581 { 582 about: "dry run ignores unknown series", 583 cmdArgs: []string{"--dry-run"}, 584 tools: []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "1.2.3-myawesomeseries-amd64"}, 585 currentVersion: "2.0.0-quantal-amd64", 586 agentVersion: "2.0.0", 587 expectedCmdOutput: `upgrade to this version by running 588 juju upgrade-juju --agent-version="2.1.3" 589 `, 590 }, 591 } 592 593 for i, test := range tests { 594 c.Logf("\ntest %d: %s", i, test.about) 595 s.Reset(c) 596 tools.DefaultBaseURL = "" 597 598 s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools) 599 600 com := newUpgradeJujuCommand(nil) 601 err := coretesting.InitCommand(com, test.cmdArgs) 602 c.Assert(err, jc.ErrorIsNil) 603 604 ctx := coretesting.Context(c) 605 err = com.Run(ctx) 606 c.Assert(err, jc.ErrorIsNil) 607 608 // Check agent version doesn't change 609 cfg, err := s.State.ModelConfig() 610 c.Assert(err, jc.ErrorIsNil) 611 agentVer, ok := cfg.AgentVersion() 612 c.Assert(ok, jc.IsTrue) 613 c.Assert(agentVer, gc.Equals, version.MustParse(test.agentVersion)) 614 output := coretesting.Stderr(ctx) 615 c.Assert(output, gc.Equals, test.expectedCmdOutput) 616 } 617 } 618 619 func (s *UpgradeJujuSuite) setUpEnvAndTools(c *gc.C, currentVersion string, agentVersion string, tools []string) { 620 current := version.MustParseBinary(currentVersion) 621 s.PatchValue(&jujuversion.Current, current.Number) 622 s.PatchValue(&arch.HostArch, func() string { return current.Arch }) 623 s.PatchValue(&series.HostSeries, func() string { return current.Series }) 624 625 toolsDir := c.MkDir() 626 updateAttrs := map[string]interface{}{ 627 "agent-version": agentVersion, 628 "agent-metadata-url": "file://" + toolsDir + "/tools", 629 } 630 631 err := s.State.UpdateModelConfig(updateAttrs, nil, nil) 632 c.Assert(err, jc.ErrorIsNil) 633 versions := make([]version.Binary, len(tools)) 634 for i, v := range tools { 635 versions[i], err = version.ParseBinary(v) 636 if err != nil { 637 c.Assert(err, jc.Satisfies, series.IsUnknownOSForSeriesError) 638 } 639 } 640 if len(versions) > 0 { 641 stor, err := filestorage.NewFileStorageWriter(toolsDir) 642 c.Assert(err, jc.ErrorIsNil) 643 envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...) 644 } 645 } 646 647 func (s *UpgradeJujuSuite) TestUpgradesDifferentMajor(c *gc.C) { 648 tests := []struct { 649 about string 650 cmdArgs []string 651 tools []string 652 currentVersion string 653 agentVersion string 654 expectedVersion string 655 expectedCmdOutput string 656 expectedLogOutput string 657 excludedLogOutput string 658 expectedErr string 659 upgradeMap map[int]version.Number 660 }{{ 661 about: "upgrade previous major to latest previous major", 662 tools: []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"}, 663 currentVersion: "5.0.0-trusty-amd64", 664 agentVersion: "4.8.5", 665 expectedVersion: "4.9.0", 666 }, { 667 about: "upgrade previous major to latest previous major --dry-run still warns", 668 tools: []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"}, 669 currentVersion: "5.0.1-trusty-amd64", 670 agentVersion: "4.8.5", 671 expectedVersion: "4.9.0", 672 }, { 673 about: "upgrade previous major to latest previous major with --agent-version", 674 cmdArgs: []string{"--agent-version=4.9.0"}, 675 tools: []string{"5.0.2-trusty-amd64", "4.9.0-trusty-amd64", "4.8.0-trusty-amd64"}, 676 currentVersion: "5.0.2-trusty-amd64", 677 agentVersion: "4.7.5", 678 expectedVersion: "4.9.0", 679 }, { 680 about: "can upgrade lower major version to current major version at minimum level", 681 cmdArgs: []string{"--agent-version=6.0.5"}, 682 tools: []string{"6.0.5-trusty-amd64", "5.9.9-trusty-amd64"}, 683 currentVersion: "6.0.0-trusty-amd64", 684 agentVersion: "5.9.8", 685 expectedVersion: "6.0.5", 686 excludedLogOutput: `incompatible with this client (6.0.0)`, 687 upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")}, 688 }, { 689 about: "can upgrade lower major version to current major version above minimum level", 690 cmdArgs: []string{"--agent-version=6.0.5"}, 691 tools: []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"}, 692 currentVersion: "6.0.1-trusty-amd64", 693 agentVersion: "5.10.8", 694 expectedVersion: "6.0.5", 695 excludedLogOutput: `incompatible with this client (6.0.1)`, 696 upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")}, 697 }, { 698 about: "can upgrade current to next major version", 699 cmdArgs: []string{"--agent-version=6.0.5"}, 700 tools: []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"}, 701 currentVersion: "5.10.8-trusty-amd64", 702 agentVersion: "5.10.8", 703 expectedVersion: "6.0.5", 704 upgradeMap: map[int]version.Number{6: version.MustParse("5.9.8")}, 705 }, { 706 about: "upgrade fails if not at minimum version", 707 cmdArgs: []string{"--agent-version=7.0.1"}, 708 tools: []string{"7.0.1-trusty-amd64"}, 709 currentVersion: "7.0.1-trusty-amd64", 710 agentVersion: "6.0.0", 711 expectedVersion: "6.0.0", 712 expectedCmdOutput: "upgrades to a new major version must first go through 6.7.8\n", 713 expectedErr: "unable to upgrade to requested version", 714 upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")}, 715 }, { 716 about: "upgrade fails if not a minor of 0", 717 cmdArgs: []string{"--agent-version=7.1.1"}, 718 tools: []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"}, 719 currentVersion: "7.0.1-trusty-amd64", 720 agentVersion: "6.7.8", 721 expectedVersion: "6.7.8", 722 expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n", 723 expectedErr: "unable to upgrade to requested version", 724 upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")}, 725 }, { 726 about: "upgrade fails if not at minimum version and not a minor of 0", 727 cmdArgs: []string{"--agent-version=7.1.1"}, 728 tools: []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"}, 729 currentVersion: "7.0.1-trusty-amd64", 730 agentVersion: "6.0.0", 731 expectedVersion: "6.0.0", 732 expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n" + 733 "upgrades to a new major version must first go through 6.7.8\n", 734 expectedErr: "unable to upgrade to requested version", 735 upgradeMap: map[int]version.Number{7: version.MustParse("6.7.8")}, 736 }} 737 for i, test := range tests { 738 c.Logf("\ntest %d: %s", i, test.about) 739 s.Reset(c) 740 tools.DefaultBaseURL = "" 741 742 s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools) 743 744 com := newUpgradeJujuCommand(test.upgradeMap) 745 err := coretesting.InitCommand(com, test.cmdArgs) 746 c.Assert(err, jc.ErrorIsNil) 747 748 ctx := coretesting.Context(c) 749 err = com.Run(ctx) 750 if test.expectedErr != "" { 751 c.Check(err, gc.ErrorMatches, test.expectedErr) 752 } else if !c.Check(err, jc.ErrorIsNil) { 753 continue 754 } 755 756 // Check agent version doesn't change 757 cfg, err := s.State.ModelConfig() 758 c.Assert(err, jc.ErrorIsNil) 759 agentVer, ok := cfg.AgentVersion() 760 c.Assert(ok, jc.IsTrue) 761 c.Check(agentVer, gc.Equals, version.MustParse(test.expectedVersion)) 762 output := coretesting.Stderr(ctx) 763 if test.expectedCmdOutput != "" { 764 c.Check(output, gc.Equals, test.expectedCmdOutput) 765 } 766 if test.expectedLogOutput != "" { 767 c.Check(strings.Replace(c.GetTestLog(), "\n", " ", -1), gc.Matches, test.expectedLogOutput) 768 } 769 if test.excludedLogOutput != "" { 770 c.Check(c.GetTestLog(), gc.Not(jc.Contains), test.excludedLogOutput) 771 } 772 } 773 } 774 775 func (s *UpgradeJujuSuite) TestUpgradeUnknownSeriesInStreams(c *gc.C) { 776 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 777 fakeAPI.addTools("2.1.0-weird-amd64") 778 fakeAPI.patch(s) 779 780 cmd := &upgradeJujuCommand{} 781 err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{}) 782 c.Assert(err, jc.ErrorIsNil) 783 784 err = modelcmd.Wrap(cmd).Run(coretesting.Context(c)) 785 c.Assert(err, gc.IsNil) 786 787 // ensure find tools was called 788 c.Assert(fakeAPI.findToolsCalled, jc.IsTrue) 789 c.Assert(fakeAPI.tools, gc.DeepEquals, []string{"2.1.0-weird-amd64", fakeAPI.nextVersion.String()}) 790 } 791 792 func (s *UpgradeJujuSuite) TestUpgradeInProgress(c *gc.C) { 793 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 794 fakeAPI.setVersionErr = ¶ms.Error{ 795 Message: "a message from the server about the problem", 796 Code: params.CodeUpgradeInProgress, 797 } 798 fakeAPI.patch(s) 799 cmd := &upgradeJujuCommand{} 800 err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{}) 801 c.Assert(err, jc.ErrorIsNil) 802 803 err = modelcmd.Wrap(cmd).Run(coretesting.Context(c)) 804 c.Assert(err, gc.ErrorMatches, "a message from the server about the problem\n"+ 805 "\n"+ 806 "Please wait for the upgrade to complete or if there was a problem with\n"+ 807 "the last upgrade that has been resolved, consider running the\n"+ 808 "upgrade-juju command with the --reset-previous-upgrade flag.", 809 ) 810 } 811 812 func (s *UpgradeJujuSuite) TestBlockUpgradeInProgress(c *gc.C) { 813 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 814 fakeAPI.setVersionErr = common.OperationBlockedError("the operation has been blocked") 815 fakeAPI.patch(s) 816 cmd := &upgradeJujuCommand{} 817 err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{}) 818 c.Assert(err, jc.ErrorIsNil) 819 820 // Block operation 821 s.BlockAllChanges(c, "TestBlockUpgradeInProgress") 822 err = modelcmd.Wrap(cmd).Run(coretesting.Context(c)) 823 s.AssertBlocked(c, err, ".*To enable changes.*") 824 } 825 826 func (s *UpgradeJujuSuite) TestResetPreviousUpgrade(c *gc.C) { 827 fakeAPI := NewFakeUpgradeJujuAPI(c, s.State) 828 fakeAPI.patch(s) 829 830 ctx := coretesting.Context(c) 831 var stdin bytes.Buffer 832 ctx.Stdin = &stdin 833 834 run := func(answer string, expect bool, args ...string) { 835 stdin.Reset() 836 if answer != "" { 837 stdin.WriteString(answer) 838 } 839 840 fakeAPI.reset() 841 842 cmd := &upgradeJujuCommand{} 843 err := coretesting.InitCommand(modelcmd.Wrap(cmd), 844 append([]string{"--reset-previous-upgrade"}, args...)) 845 c.Assert(err, jc.ErrorIsNil) 846 err = modelcmd.Wrap(cmd).Run(ctx) 847 if expect { 848 c.Assert(err, jc.ErrorIsNil) 849 } else { 850 c.Assert(err, gc.ErrorMatches, "previous upgrade not reset and no new upgrade triggered") 851 } 852 853 c.Assert(fakeAPI.abortCurrentUpgradeCalled, gc.Equals, expect) 854 expectedVersion := version.Number{} 855 if expect { 856 expectedVersion = fakeAPI.nextVersion.Number 857 } 858 c.Assert(fakeAPI.setVersionCalledWith, gc.Equals, expectedVersion) 859 } 860 861 const expectUpgrade = true 862 const expectNoUpgrade = false 863 864 // EOF on stdin - equivalent to answering no. 865 run("", expectNoUpgrade) 866 867 // -y on command line - no confirmation required 868 run("", expectUpgrade, "-y") 869 870 // --yes on command line - no confirmation required 871 run("", expectUpgrade, "--yes") 872 873 // various ways of saying "yes" to the prompt 874 for _, answer := range []string{"y", "Y", "yes", "YES"} { 875 run(answer, expectUpgrade) 876 } 877 878 // various ways of saying "no" to the prompt 879 for _, answer := range []string{"n", "N", "no", "foo"} { 880 run(answer, expectNoUpgrade) 881 } 882 } 883 884 func NewFakeUpgradeJujuAPI(c *gc.C, st *state.State) *fakeUpgradeJujuAPI { 885 nextVersion := version.Binary{ 886 Number: jujuversion.Current, 887 Arch: arch.HostArch(), 888 Series: series.HostSeries(), 889 } 890 nextVersion.Minor++ 891 return &fakeUpgradeJujuAPI{ 892 c: c, 893 st: st, 894 nextVersion: nextVersion, 895 } 896 } 897 898 type fakeUpgradeJujuAPI struct { 899 c *gc.C 900 st *state.State 901 nextVersion version.Binary 902 setVersionErr error 903 abortCurrentUpgradeCalled bool 904 setVersionCalledWith version.Number 905 tools []string 906 findToolsCalled bool 907 } 908 909 func (a *fakeUpgradeJujuAPI) reset() { 910 a.setVersionErr = nil 911 a.abortCurrentUpgradeCalled = false 912 a.setVersionCalledWith = version.Number{} 913 a.tools = []string{} 914 a.findToolsCalled = false 915 } 916 917 func (a *fakeUpgradeJujuAPI) patch(s *UpgradeJujuSuite) { 918 s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) { 919 return a, nil 920 }) 921 s.PatchValue(&getModelConfigAPI, func(*upgradeJujuCommand) (modelConfigAPI, error) { 922 return a, nil 923 }) 924 s.PatchValue(&getControllerAPI, func(*upgradeJujuCommand) (controllerAPI, error) { 925 return a, nil 926 }) 927 } 928 929 func (a *fakeUpgradeJujuAPI) ModelConfig() (map[string]interface{}, error) { 930 controllerModel, err := a.st.ControllerModel() 931 if err != nil { 932 return make(map[string]interface{}), err 933 } 934 return map[string]interface{}{ 935 "uuid": controllerModel.UUID(), 936 }, nil 937 } 938 939 func (a *fakeUpgradeJujuAPI) addTools(tools ...string) { 940 for _, tool := range tools { 941 a.tools = append(a.tools, tool) 942 } 943 } 944 945 func (a *fakeUpgradeJujuAPI) ModelGet() (map[string]interface{}, error) { 946 config, err := a.st.ModelConfig() 947 if err != nil { 948 return make(map[string]interface{}), err 949 } 950 return config.AllAttrs(), nil 951 } 952 953 func (a *fakeUpgradeJujuAPI) FindTools(majorVersion, minorVersion int, series, arch string) ( 954 result params.FindToolsResult, err error, 955 ) { 956 a.findToolsCalled = true 957 a.tools = append(a.tools, a.nextVersion.String()) 958 tools := toolstesting.MakeTools(a.c, a.c.MkDir(), "released", a.tools) 959 return params.FindToolsResult{ 960 List: tools, 961 Error: nil, 962 }, nil 963 } 964 965 func (a *fakeUpgradeJujuAPI) UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (coretools.List, error) { 966 panic("not implemented") 967 } 968 969 func (a *fakeUpgradeJujuAPI) AbortCurrentUpgrade() error { 970 a.abortCurrentUpgradeCalled = true 971 return nil 972 } 973 974 func (a *fakeUpgradeJujuAPI) SetModelAgentVersion(v version.Number) error { 975 a.setVersionCalledWith = v 976 return a.setVersionErr 977 } 978 979 func (a *fakeUpgradeJujuAPI) Close() error { 980 return nil 981 } 982 983 // Mock an API with no state 984 type fakeUpgradeJujuAPINoState struct { 985 upgradeJujuAPI 986 name string 987 uuid string 988 controllerUUID string 989 agentVersion string 990 tools coretools.List 991 modelAgentVersion version.Number 992 } 993 994 func (a *fakeUpgradeJujuAPINoState) Close() error { 995 return nil 996 } 997 998 func (a *fakeUpgradeJujuAPINoState) FindTools(majorVersion, minorVersion int, series, arch string) (params.FindToolsResult, error) { 999 var result params.FindToolsResult 1000 if len(a.tools) == 0 { 1001 result.Error = common.ServerError(errors.NotFoundf("tools")) 1002 } else { 1003 result.List = a.tools 1004 } 1005 return result, nil 1006 } 1007 1008 func (a *fakeUpgradeJujuAPINoState) UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (coretools.List, error) { 1009 a.tools = coretools.List{&coretools.Tools{Version: vers}} 1010 for _, s := range additionalSeries { 1011 v := vers 1012 v.Series = s 1013 a.tools = append(a.tools, &coretools.Tools{Version: v}) 1014 } 1015 return a.tools, nil 1016 } 1017 1018 func (a *fakeUpgradeJujuAPINoState) SetModelAgentVersion(version version.Number) error { 1019 a.modelAgentVersion = version 1020 return nil 1021 } 1022 1023 func (a *fakeUpgradeJujuAPINoState) ModelGet() (map[string]interface{}, error) { 1024 return dummy.SampleConfig().Merge(map[string]interface{}{ 1025 "name": a.name, 1026 "uuid": a.uuid, 1027 "controller-uuid": a.controllerUUID, 1028 "agent-version": a.agentVersion, 1029 }), nil 1030 }