launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/cmd/juju/bootstrap_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "bytes" 8 "fmt" 9 "strings" 10 11 gc "launchpad.net/gocheck" 12 13 "launchpad.net/juju-core/cmd" 14 "launchpad.net/juju-core/constraints" 15 "launchpad.net/juju-core/environs" 16 "launchpad.net/juju-core/environs/configstore" 17 "launchpad.net/juju-core/environs/filestorage" 18 "launchpad.net/juju-core/environs/imagemetadata" 19 imtesting "launchpad.net/juju-core/environs/imagemetadata/testing" 20 "launchpad.net/juju-core/environs/simplestreams" 21 "launchpad.net/juju-core/environs/storage" 22 "launchpad.net/juju-core/environs/sync" 23 envtesting "launchpad.net/juju-core/environs/testing" 24 envtools "launchpad.net/juju-core/environs/tools" 25 ttesting "launchpad.net/juju-core/environs/tools/testing" 26 "launchpad.net/juju-core/errors" 27 "launchpad.net/juju-core/provider/dummy" 28 coretesting "launchpad.net/juju-core/testing" 29 "launchpad.net/juju-core/testing/testbase" 30 coretools "launchpad.net/juju-core/tools" 31 "launchpad.net/juju-core/version" 32 ) 33 34 type BootstrapSuite struct { 35 testbase.LoggingSuite 36 coretesting.MgoSuite 37 envtesting.ToolsFixture 38 } 39 40 var _ = gc.Suite(&BootstrapSuite{}) 41 42 func (s *BootstrapSuite) SetUpSuite(c *gc.C) { 43 s.LoggingSuite.SetUpSuite(c) 44 s.MgoSuite.SetUpSuite(c) 45 } 46 47 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 48 s.LoggingSuite.SetUpTest(c) 49 s.MgoSuite.SetUpTest(c) 50 s.ToolsFixture.SetUpTest(c) 51 52 // Set up a local source with tools. 53 sourceDir := createToolsSource(c, vAll) 54 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 55 } 56 57 func (s *BootstrapSuite) TearDownSuite(c *gc.C) { 58 s.MgoSuite.TearDownSuite(c) 59 s.LoggingSuite.TearDownSuite(c) 60 } 61 62 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 63 s.ToolsFixture.TearDownTest(c) 64 s.MgoSuite.TearDownTest(c) 65 s.LoggingSuite.TearDownTest(c) 66 dummy.Reset() 67 } 68 69 type bootstrapRetryTest struct { 70 info string 71 args []string 72 expectedAllowRetry []bool 73 err string 74 // If version != "", version.Current will be 75 // set to it for the duration of the test. 76 version string 77 // If addVersionToSource is true, then "version" 78 // above will be populated in the tools source. 79 addVersionToSource bool 80 } 81 82 var bootstrapRetryTests = []bootstrapRetryTest{{ 83 info: "no tools uploaded, first check has no retries; no matching binary in source; sync fails with no second attempt", 84 expectedAllowRetry: []bool{false}, 85 err: "cannot find bootstrap tools: no matching tools available", 86 version: "1.16.0-precise-amd64", 87 }, { 88 info: "no tools uploaded, first check has no retries; matching binary in source; check after sync has retries", 89 expectedAllowRetry: []bool{false, true}, 90 err: "cannot find bootstrap tools: tools not found", 91 version: "1.16.0-precise-amd64", 92 addVersionToSource: true, 93 }, { 94 info: "no tools uploaded, first check has no retries; no matching binary in source; check after upload has retries", 95 expectedAllowRetry: []bool{false, true}, 96 err: "cannot find bootstrap tools: tools not found", 97 version: "1.15.1-precise-amd64", // dev version to force upload 98 }, { 99 info: "new tools uploaded, so we want to allow retries to give them a chance at showing up", 100 args: []string{"--upload-tools"}, 101 expectedAllowRetry: []bool{true}, 102 err: "cannot find bootstrap tools: no matching tools available", 103 }} 104 105 // Test test checks that bootstrap calls FindTools with the expected allowRetry flag. 106 func (s *BootstrapSuite) TestAllowRetries(c *gc.C) { 107 for i, test := range bootstrapRetryTests { 108 c.Logf("test %d: %s\n", i, test.info) 109 s.runAllowRetriesTest(c, test) 110 } 111 } 112 113 func (s *BootstrapSuite) runAllowRetriesTest(c *gc.C, test bootstrapRetryTest) { 114 toolsVersions := envtesting.VAll 115 if test.version != "" { 116 testVersion := version.MustParseBinary(test.version) 117 restore := testbase.PatchValue(&version.Current, testVersion) 118 defer restore() 119 if test.addVersionToSource { 120 toolsVersions = append([]version.Binary{}, toolsVersions...) 121 toolsVersions = append(toolsVersions, testVersion) 122 } 123 } 124 _, fake := makeEmptyFakeHome(c) 125 defer fake.Restore() 126 sourceDir := createToolsSource(c, toolsVersions) 127 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 128 129 var findToolsRetryValues []bool 130 mockFindTools := func(cloudInst environs.ConfigGetter, majorVersion, minorVersion int, 131 filter coretools.Filter, allowRetry bool) (list coretools.List, err error) { 132 findToolsRetryValues = append(findToolsRetryValues, allowRetry) 133 return nil, errors.NotFoundf("tools") 134 } 135 136 restore := envtools.TestingPatchBootstrapFindTools(mockFindTools) 137 defer restore() 138 139 _, errc := runCommand(nullContext(), new(BootstrapCommand), test.args...) 140 err := <-errc 141 c.Check(findToolsRetryValues, gc.DeepEquals, test.expectedAllowRetry) 142 c.Check(err, gc.ErrorMatches, test.err) 143 } 144 145 func (s *BootstrapSuite) TestTest(c *gc.C) { 146 s.PatchValue(&sync.Upload, mockUploadTools) 147 for i, test := range bootstrapTests { 148 c.Logf("\ntest %d: %s", i, test.info) 149 test.run(c) 150 } 151 } 152 153 type bootstrapTest struct { 154 info string 155 // binary version string used to set version.Current 156 version string 157 sync bool 158 args []string 159 err string 160 // binary version strings for expected tools; if set, no default tools 161 // will be uploaded before running the test. 162 uploads []string 163 constraints constraints.Value 164 } 165 166 func (test bootstrapTest) run(c *gc.C) { 167 // Create home with dummy provider and remove all 168 // of its envtools. 169 env, fake := makeEmptyFakeHome(c) 170 defer fake.Restore() 171 172 if test.version != "" { 173 origVersion := version.Current 174 version.Current = version.MustParseBinary(test.version) 175 defer func() { version.Current = origVersion }() 176 } 177 178 uploadCount := len(test.uploads) 179 if uploadCount == 0 { 180 usefulVersion := version.Current 181 usefulVersion.Series = env.Config().DefaultSeries() 182 envtesting.AssertUploadFakeToolsVersions(c, env.Storage(), usefulVersion) 183 } 184 185 // Run command and check for uploads. 186 opc, errc := runCommand(nullContext(), new(BootstrapCommand), test.args...) 187 if uploadCount > 0 { 188 for i := 0; i < uploadCount; i++ { 189 c.Check((<-opc).(dummy.OpPutFile).Env, gc.Equals, "peckham") 190 } 191 list, err := envtools.FindTools( 192 env, version.Current.Major, version.Current.Minor, coretools.Filter{}, envtools.DoNotAllowRetry) 193 c.Check(err, gc.IsNil) 194 c.Logf("found: " + list.String()) 195 urls := list.URLs() 196 c.Check(urls, gc.HasLen, len(test.uploads)) 197 for _, v := range test.uploads { 198 c.Logf("seeking: " + v) 199 vers := version.MustParseBinary(v) 200 _, found := urls[vers] 201 c.Check(found, gc.Equals, true) 202 } 203 } 204 205 // Check for remaining operations/errors. 206 if test.err != "" { 207 c.Check(<-errc, gc.ErrorMatches, test.err) 208 return 209 } 210 if !c.Check(<-errc, gc.IsNil) { 211 return 212 } 213 if len(test.uploads) > 0 { 214 indexFile := (<-opc).(dummy.OpPutFile) 215 c.Check(indexFile.FileName, gc.Equals, "tools/streams/v1/index.json") 216 productFile := (<-opc).(dummy.OpPutFile) 217 c.Check(productFile.FileName, gc.Equals, "tools/streams/v1/com.ubuntu.juju:released:tools.json") 218 } 219 opPutBootstrapVerifyFile := (<-opc).(dummy.OpPutFile) 220 c.Check(opPutBootstrapVerifyFile.Env, gc.Equals, "peckham") 221 c.Check(opPutBootstrapVerifyFile.FileName, gc.Equals, environs.VerificationFilename) 222 223 opPutBootstrapInitFile := (<-opc).(dummy.OpPutFile) 224 c.Check(opPutBootstrapInitFile.Env, gc.Equals, "peckham") 225 c.Check(opPutBootstrapInitFile.FileName, gc.Equals, "provider-state") 226 227 opBootstrap := (<-opc).(dummy.OpBootstrap) 228 c.Check(opBootstrap.Env, gc.Equals, "peckham") 229 c.Check(opBootstrap.Constraints, gc.DeepEquals, test.constraints) 230 231 store, err := configstore.Default() 232 c.Assert(err, gc.IsNil) 233 // Check a CA cert/key was generated by reloading the environment. 234 env, err = environs.NewFromName("peckham", store) 235 c.Assert(err, gc.IsNil) 236 _, hasCert := env.Config().CACert() 237 c.Check(hasCert, gc.Equals, true) 238 _, hasKey := env.Config().CAPrivateKey() 239 c.Check(hasKey, gc.Equals, true) 240 } 241 242 var bootstrapTests = []bootstrapTest{{ 243 info: "no args, no error, no uploads, no constraints", 244 }, { 245 info: "bad arg", 246 args: []string{"twiddle"}, 247 err: `unrecognized args: \["twiddle"\]`, 248 }, { 249 info: "bad --constraints", 250 args: []string{"--constraints", "bad=wrong"}, 251 err: `invalid value "bad=wrong" for flag --constraints: unknown constraint "bad"`, 252 }, { 253 info: "bad --series", 254 args: []string{"--series", "bad1"}, 255 err: `invalid value "bad1" for flag --series: invalid series name "bad1"`, 256 }, { 257 info: "lonely --series", 258 args: []string{"--series", "fine"}, 259 err: `--series requires --upload-tools`, 260 }, { 261 info: "bad environment", 262 version: "1.2.3-precise-amd64", 263 args: []string{"-e", "brokenenv"}, 264 err: `dummy.Bootstrap is broken`, 265 }, { 266 info: "constraints", 267 args: []string{"--constraints", "mem=4G cpu-cores=4"}, 268 constraints: constraints.MustParse("mem=4G cpu-cores=4"), 269 }, { 270 info: "--upload-tools picks all reasonable series", 271 version: "1.2.3-saucy-amd64", 272 args: []string{"--upload-tools"}, 273 uploads: []string{ 274 "1.2.3.1-saucy-amd64", // from version.Current 275 "1.2.3.1-raring-amd64", // from env.Config().DefaultSeries() 276 "1.2.3.1-precise-amd64", // from environs/config.DefaultSeries 277 }, 278 }, { 279 info: "--upload-tools only uploads each file once", 280 version: "1.2.3-precise-amd64", 281 args: []string{"--upload-tools"}, 282 uploads: []string{ 283 "1.2.3.1-raring-amd64", 284 "1.2.3.1-precise-amd64", 285 }, 286 }, { 287 info: "--upload-tools rejects invalid series", 288 version: "1.2.3-saucy-amd64", 289 args: []string{"--upload-tools", "--series", "ping,ping,pong"}, 290 err: `invalid series "ping"`, 291 }, { 292 info: "--upload-tools always bumps build number", 293 version: "1.2.3.4-raring-amd64", 294 args: []string{"--upload-tools"}, 295 uploads: []string{ 296 "1.2.3.5-raring-amd64", 297 "1.2.3.5-precise-amd64", 298 }, 299 }} 300 301 func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) { 302 env, fake := makeEmptyFakeHome(c) 303 defer fake.Restore() 304 defaultSeriesVersion := version.Current 305 defaultSeriesVersion.Series = env.Config().DefaultSeries() 306 307 ctx := coretesting.Context(c) 308 code := cmd.Main(&BootstrapCommand{}, ctx, nil) 309 c.Check(code, gc.Equals, 0) 310 311 ctx2 := coretesting.Context(c) 312 code2 := cmd.Main(&BootstrapCommand{}, ctx2, nil) 313 c.Check(code2, gc.Equals, 1) 314 c.Check(coretesting.Stderr(ctx2), gc.Equals, "error: environment is already bootstrapped\n") 315 c.Check(coretesting.Stdout(ctx2), gc.Equals, "") 316 } 317 318 func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) { 319 s.PatchValue(&version.Current.Number, version.MustParse("1.2.0")) 320 env, fake := makeEmptyFakeHome(c) 321 defer fake.Restore() 322 323 // Bootstrap the environment with an invalid source. 324 // The command returns with an error. 325 ctx := coretesting.Context(c) 326 code := cmd.Main(&BootstrapCommand{}, ctx, []string{"--metadata-source", c.MkDir()}) 327 c.Check(code, gc.Equals, 1) 328 329 // Now check that there are no tools available. 330 _, err := envtools.FindTools( 331 env, version.Current.Major, version.Current.Minor, coretools.Filter{}, envtools.DoNotAllowRetry) 332 c.Assert(err, gc.FitsTypeOf, errors.NotFoundf("")) 333 } 334 335 // createImageMetadata creates some image metadata in a local directory. 336 func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) { 337 // Generate some image metadata. 338 im := []*imagemetadata.ImageMetadata{ 339 { 340 Id: "1234", 341 Arch: "amd64", 342 Version: "13.04", 343 RegionName: "region", 344 Endpoint: "endpoint", 345 }, 346 } 347 cloudSpec := &simplestreams.CloudSpec{ 348 Region: "region", 349 Endpoint: "endpoint", 350 } 351 sourceDir := c.MkDir() 352 sourceStor, err := filestorage.NewFileStorageWriter(sourceDir, filestorage.UseDefaultTmpDir) 353 c.Assert(err, gc.IsNil) 354 err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor) 355 c.Assert(err, gc.IsNil) 356 return sourceDir, im 357 } 358 359 // checkImageMetadata checks that the environment contains the expected image metadata. 360 func checkImageMetadata(c *gc.C, stor storage.StorageReader, expected []*imagemetadata.ImageMetadata) { 361 metadata := imtesting.ParseMetadataFromStorage(c, stor) 362 c.Assert(metadata, gc.HasLen, 1) 363 c.Assert(expected[0], gc.DeepEquals, metadata[0]) 364 } 365 366 func (s *BootstrapSuite) TestUploadLocalImageMetadata(c *gc.C) { 367 sourceDir, expected := createImageMetadata(c) 368 env, fake := makeEmptyFakeHome(c) 369 defer fake.Restore() 370 371 // Bootstrap the environment with the valid source. 372 ctx := coretesting.Context(c) 373 code := cmd.Main(&BootstrapCommand{}, ctx, []string{"--metadata-source", sourceDir}) 374 c.Check(code, gc.Equals, 0) 375 c.Assert(imagemetadata.DefaultBaseURL, gc.Equals, imagemetadata.UbuntuCloudImagesURL) 376 377 // Now check the image metadata has been uploaded. 378 checkImageMetadata(c, env.Storage(), expected) 379 } 380 381 func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) { 382 sourceDir := createToolsSource(c, vAll) 383 s.PatchValue(&version.Current.Number, version.MustParse("1.2.0")) 384 env, fake := makeEmptyFakeHome(c) 385 defer fake.Restore() 386 387 // Bootstrap the environment with the valid source. 388 // The bootstrapping has to show no error, because the tools 389 // are automatically synchronized. 390 ctx := coretesting.Context(c) 391 code := cmd.Main(&BootstrapCommand{}, ctx, []string{"--metadata-source", sourceDir}) 392 c.Check(code, gc.Equals, 0) 393 394 // Now check the available tools which are the 1.2.0 envtools. 395 checkTools(c, env, v120All) 396 } 397 398 func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, series string) environs.Environ { 399 s.PatchValue(&sync.Upload, mockUploadTools) 400 sourceDir := createToolsSource(c, vAll) 401 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 402 403 // Change the tools location to be the test location and also 404 // the version and ensure their later restoring. 405 // Set the current version to be something for which there are no tools 406 // so we can test that an upload is forced. 407 origVersion := version.Current 408 version.Current.Number = version.MustParse(vers) 409 version.Current.Series = series 410 s.AddCleanup(func(*gc.C) { version.Current = origVersion }) 411 412 // Create home with dummy provider and remove all 413 // of its envtools. 414 env, fake := makeEmptyFakeHome(c) 415 s.AddCleanup(func(*gc.C) { fake.Restore() }) 416 return env 417 } 418 419 func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) { 420 otherSeries := "precise" 421 if otherSeries == version.Current.Series { 422 otherSeries = "raring" 423 } 424 env := s.setupAutoUploadTest(c, "1.7.3", otherSeries) 425 // Run command and check for that upload has been run for tools matching the current juju version. 426 opc, errc := runCommand(nullContext(), new(BootstrapCommand)) 427 c.Assert(<-errc, gc.IsNil) 428 c.Assert((<-opc).(dummy.OpPutFile).Env, gc.Equals, "peckham") 429 list, err := envtools.FindTools(env, version.Current.Major, version.Current.Minor, coretools.Filter{}, false) 430 c.Assert(err, gc.IsNil) 431 c.Logf("found: " + list.String()) 432 urls := list.URLs() 433 c.Assert(urls, gc.HasLen, 2) 434 expectedVers := []version.Binary{ 435 version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", otherSeries, version.Current.Arch)), 436 version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", version.Current.Series, version.Current.Arch)), 437 } 438 for _, vers := range expectedVers { 439 c.Logf("seeking: " + vers.String()) 440 _, found := urls[vers] 441 c.Check(found, gc.Equals, true) 442 } 443 } 444 445 func (s *BootstrapSuite) TestAutoUploadOnlyForDev(c *gc.C) { 446 s.setupAutoUploadTest(c, "1.8.3", "precise") 447 _, errc := runCommand(nullContext(), new(BootstrapCommand)) 448 err := <-errc 449 c.Assert(err, gc.ErrorMatches, "cannot find bootstrap tools: no matching tools available") 450 } 451 452 func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) { 453 s.setupAutoUploadTest(c, "1.8.3", "precise") 454 context := coretesting.Context(c) 455 code := cmd.Main(&BootstrapCommand{}, context, nil) 456 c.Assert(code, gc.Equals, 1) 457 errText := context.Stderr.(*bytes.Buffer).String() 458 errText = strings.Replace(errText, "\n", "", -1) 459 expectedErrText := "error: cannot find bootstrap tools: no matching tools available" 460 c.Assert(errText, gc.Matches, expectedErrText) 461 } 462 463 func uploadToolsAlwaysFails(stor storage.Storage, forceVersion *version.Number, series ...string) (*coretools.Tools, error) { 464 return nil, fmt.Errorf("an error") 465 } 466 467 func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) { 468 s.setupAutoUploadTest(c, "1.7.3", "precise") 469 s.PatchValue(&sync.Upload, uploadToolsAlwaysFails) 470 context := coretesting.Context(c) 471 code := cmd.Main(&BootstrapCommand{}, context, nil) 472 c.Assert(code, gc.Equals, 1) 473 errText := context.Stderr.(*bytes.Buffer).String() 474 errText = strings.Replace(errText, "\n", "", -1) 475 expectedErrText := "error: cannot find bootstrap tools: an error" 476 c.Assert(errText, gc.Matches, expectedErrText) 477 } 478 479 func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) { 480 _, fake := makeEmptyFakeHome(c) 481 defer fake.Restore() 482 opc, errc := runCommand(nullContext(), new(BootstrapCommand), "-e", "brokenenv") 483 err := <-errc 484 c.Assert(err, gc.ErrorMatches, "dummy.Bootstrap is broken") 485 var opDestroy *dummy.OpDestroy 486 for opDestroy == nil { 487 select { 488 case op := <-opc: 489 switch op := op.(type) { 490 case dummy.OpDestroy: 491 opDestroy = &op 492 } 493 default: 494 c.Error("expected call to env.Destroy") 495 return 496 } 497 } 498 c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken") 499 } 500 501 // createToolsSource writes the mock tools and metadata into a temporary 502 // directory and returns it. 503 func createToolsSource(c *gc.C, versions []version.Binary) string { 504 versionStrings := make([]string, len(versions)) 505 for i, vers := range versions { 506 versionStrings[i] = vers.String() 507 } 508 source := c.MkDir() 509 ttesting.MakeTools(c, source, "releases", versionStrings) 510 return source 511 } 512 513 // makeEmptyFakeHome creates a faked home without envtools. 514 func makeEmptyFakeHome(c *gc.C) (environs.Environ, *coretesting.FakeHome) { 515 fake := coretesting.MakeFakeHome(c, envConfig) 516 dummy.Reset() 517 store, err := configstore.Default() 518 c.Assert(err, gc.IsNil) 519 env, err := environs.PrepareFromName("peckham", store) 520 c.Assert(err, gc.IsNil) 521 envtesting.RemoveAllTools(c, env) 522 return env, fake 523 } 524 525 // checkTools check if the environment contains the passed envtools. 526 func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) { 527 list, err := envtools.FindTools( 528 env, version.Current.Major, version.Current.Minor, coretools.Filter{}, envtools.DoNotAllowRetry) 529 c.Check(err, gc.IsNil) 530 c.Logf("found: " + list.String()) 531 urls := list.URLs() 532 c.Check(urls, gc.HasLen, len(expected)) 533 } 534 535 var ( 536 v100d64 = version.MustParseBinary("1.0.0-raring-amd64") 537 v100p64 = version.MustParseBinary("1.0.0-precise-amd64") 538 v100q32 = version.MustParseBinary("1.0.0-quantal-i386") 539 v100q64 = version.MustParseBinary("1.0.0-quantal-amd64") 540 v120d64 = version.MustParseBinary("1.2.0-raring-amd64") 541 v120p64 = version.MustParseBinary("1.2.0-precise-amd64") 542 v120q32 = version.MustParseBinary("1.2.0-quantal-i386") 543 v120q64 = version.MustParseBinary("1.2.0-quantal-amd64") 544 v190p32 = version.MustParseBinary("1.9.0-precise-i386") 545 v190q64 = version.MustParseBinary("1.9.0-quantal-amd64") 546 v200p64 = version.MustParseBinary("2.0.0-precise-amd64") 547 v100All = []version.Binary{ 548 v100d64, v100p64, v100q64, v100q32, 549 } 550 v120All = []version.Binary{ 551 v120d64, v120p64, v120q64, v120q32, 552 } 553 vAll = []version.Binary{ 554 v100d64, v100p64, v100q32, v100q64, 555 v120d64, v120p64, v120q32, v120q64, 556 v190p32, v190q64, 557 v200p64, 558 } 559 )