github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/bootstrap/bootstrap_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package bootstrap_test 5 6 import ( 7 "crypto/sha256" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "runtime" 15 "strings" 16 17 "github.com/juju/errors" 18 jc "github.com/juju/testing/checkers" 19 "github.com/juju/utils/arch" 20 "github.com/juju/utils/series" 21 "github.com/juju/version" 22 gc "gopkg.in/check.v1" 23 24 "github.com/juju/juju/cloudconfig/instancecfg" 25 "github.com/juju/juju/cmd/modelcmd" 26 "github.com/juju/juju/constraints" 27 "github.com/juju/juju/environs" 28 "github.com/juju/juju/environs/bootstrap" 29 "github.com/juju/juju/environs/config" 30 "github.com/juju/juju/environs/filestorage" 31 "github.com/juju/juju/environs/gui" 32 "github.com/juju/juju/environs/imagemetadata" 33 "github.com/juju/juju/environs/simplestreams" 34 sstesting "github.com/juju/juju/environs/simplestreams/testing" 35 "github.com/juju/juju/environs/storage" 36 "github.com/juju/juju/environs/sync" 37 envtesting "github.com/juju/juju/environs/testing" 38 envtools "github.com/juju/juju/environs/tools" 39 "github.com/juju/juju/juju" 40 "github.com/juju/juju/provider/dummy" 41 coretesting "github.com/juju/juju/testing" 42 "github.com/juju/juju/tools" 43 jujuversion "github.com/juju/juju/version" 44 ) 45 46 const ( 47 useDefaultKeys = true 48 noKeysDefined = false 49 ) 50 51 type bootstrapSuite struct { 52 coretesting.BaseSuite 53 envtesting.ToolsFixture 54 } 55 56 var _ = gc.Suite(&bootstrapSuite{}) 57 58 func (s *bootstrapSuite) SetUpTest(c *gc.C) { 59 s.BaseSuite.SetUpTest(c) 60 s.ToolsFixture.SetUpTest(c) 61 62 s.PatchValue(&juju.JujuPublicKey, sstesting.SignedMetadataPublicKey) 63 storageDir := c.MkDir() 64 s.PatchValue(&envtools.DefaultBaseURL, storageDir) 65 stor, err := filestorage.NewFileStorageWriter(storageDir) 66 c.Assert(err, jc.ErrorIsNil) 67 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 68 envtesting.UploadFakeTools(c, stor, "released", "released") 69 70 // Patch the function used to retrieve GUI archive info from simplestreams. 71 s.PatchValue(bootstrap.GUIFetchMetadata, func(string, ...simplestreams.DataSource) ([]*gui.Metadata, error) { 72 return nil, nil 73 }) 74 } 75 76 func (s *bootstrapSuite) TearDownTest(c *gc.C) { 77 s.ToolsFixture.TearDownTest(c) 78 s.BaseSuite.TearDownTest(c) 79 } 80 81 func (s *bootstrapSuite) TestBootstrapNeedsSettings(c *gc.C) { 82 env := newEnviron("bar", noKeysDefined, nil) 83 s.setDummyStorage(c, env) 84 fixEnv := func(key string, value interface{}) { 85 cfg, err := env.Config().Apply(map[string]interface{}{ 86 key: value, 87 }) 88 c.Assert(err, jc.ErrorIsNil) 89 env.cfg = cfg 90 } 91 92 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 93 c.Assert(err, gc.ErrorMatches, "model configuration has no admin-secret") 94 95 fixEnv("admin-secret", "whatever") 96 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 97 c.Assert(err, gc.ErrorMatches, "model configuration has no ca-cert") 98 99 fixEnv("ca-cert", coretesting.CACert) 100 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 101 c.Assert(err, gc.ErrorMatches, "model configuration has no ca-private-key") 102 103 fixEnv("ca-private-key", coretesting.CAKey) 104 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 105 c.Assert(err, jc.ErrorIsNil) 106 } 107 108 func (s *bootstrapSuite) TestBootstrapEmptyConstraints(c *gc.C) { 109 env := newEnviron("foo", useDefaultKeys, nil) 110 s.setDummyStorage(c, env) 111 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 112 c.Assert(err, jc.ErrorIsNil) 113 c.Assert(env.bootstrapCount, gc.Equals, 1) 114 env.args.AvailableTools = nil 115 c.Assert(env.args, gc.DeepEquals, environs.BootstrapParams{}) 116 } 117 118 func (s *bootstrapSuite) TestBootstrapSpecifiedConstraints(c *gc.C) { 119 env := newEnviron("foo", useDefaultKeys, nil) 120 s.setDummyStorage(c, env) 121 bootstrapCons := constraints.MustParse("cpu-cores=3 mem=7G") 122 modelCons := constraints.MustParse("cpu-cores=2 mem=4G") 123 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 124 BootstrapConstraints: bootstrapCons, 125 ModelConstraints: modelCons, 126 }) 127 c.Assert(err, jc.ErrorIsNil) 128 c.Assert(env.bootstrapCount, gc.Equals, 1) 129 c.Assert(env.args.BootstrapConstraints, gc.DeepEquals, bootstrapCons) 130 c.Assert(env.args.ModelConstraints, gc.DeepEquals, modelCons) 131 } 132 133 func (s *bootstrapSuite) TestBootstrapSpecifiedBootstrapSeries(c *gc.C) { 134 env := newEnviron("foo", useDefaultKeys, nil) 135 s.setDummyStorage(c, env) 136 cfg, err := env.Config().Apply(map[string]interface{}{ 137 "default-series": "wily", 138 }) 139 c.Assert(err, jc.ErrorIsNil) 140 env.cfg = cfg 141 142 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 143 BootstrapSeries: "trusty", 144 }) 145 c.Assert(err, jc.ErrorIsNil) 146 c.Check(env.bootstrapCount, gc.Equals, 1) 147 c.Check(env.args.BootstrapSeries, gc.Equals, "trusty") 148 c.Check(env.args.AvailableTools.AllSeries(), jc.SameContents, []string{"trusty"}) 149 } 150 151 func (s *bootstrapSuite) TestBootstrapSpecifiedPlacement(c *gc.C) { 152 env := newEnviron("foo", useDefaultKeys, nil) 153 s.setDummyStorage(c, env) 154 placement := "directive" 155 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{Placement: placement}) 156 c.Assert(err, jc.ErrorIsNil) 157 c.Assert(env.bootstrapCount, gc.Equals, 1) 158 c.Assert(env.args.Placement, gc.DeepEquals, placement) 159 } 160 161 func (s *bootstrapSuite) TestBootstrapImage(c *gc.C) { 162 s.PatchValue(&series.HostSeries, func() string { return "precise" }) 163 s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 }) 164 165 metadataDir, metadata := createImageMetadata(c) 166 stor, err := filestorage.NewFileStorageWriter(metadataDir) 167 c.Assert(err, jc.ErrorIsNil) 168 envtesting.UploadFakeTools(c, stor, "released", "released") 169 170 env := bootstrapEnvironWithRegion{ 171 newEnviron("foo", useDefaultKeys, nil), 172 simplestreams.CloudSpec{ 173 Region: "nether", 174 Endpoint: "hearnoretheir", 175 }, 176 } 177 s.setDummyStorage(c, env.bootstrapEnviron) 178 179 bootstrapCons := constraints.MustParse("arch=amd64") 180 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 181 BootstrapImage: "img-id", 182 BootstrapSeries: "precise", 183 BootstrapConstraints: bootstrapCons, 184 MetadataDir: metadataDir, 185 }) 186 c.Assert(err, jc.ErrorIsNil) 187 c.Assert(env.bootstrapCount, gc.Equals, 1) 188 c.Assert(env.args.ImageMetadata, gc.HasLen, 1) 189 c.Assert(env.args.ImageMetadata[0], jc.DeepEquals, &imagemetadata.ImageMetadata{ 190 Id: "img-id", 191 Arch: "amd64", 192 Version: "12.04", 193 RegionName: "nether", 194 Endpoint: "hearnoretheir", 195 Stream: "released", 196 }) 197 c.Assert(env.instanceConfig.CustomImageMetadata, gc.HasLen, 2) 198 c.Assert(env.instanceConfig.CustomImageMetadata[0], jc.DeepEquals, metadata[0]) 199 c.Assert(env.instanceConfig.CustomImageMetadata[1], jc.DeepEquals, env.args.ImageMetadata[0]) 200 c.Assert(env.instanceConfig.Constraints, jc.DeepEquals, bootstrapCons) 201 } 202 203 // TestBootstrapImageMetadataFromAllSources tests that we are looking for 204 // image metadata in all data sources available to environment. 205 // Abandoning look up too soon led to misleading bootstrap failures: 206 // Juju reported no images available for a particular configuration, 207 // despite image metadata in other data sources compatible with the same configuration as well. 208 // Related to bug#1560625. 209 func (s *bootstrapSuite) TestBootstrapImageMetadataFromAllSources(c *gc.C) { 210 s.PatchValue(&series.HostSeries, func() string { return "raring" }) 211 s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 }) 212 213 // Ensure that we can find at least one image metadata 214 // early on in the image metadata lookup. 215 // We should continue looking despite it. 216 metadataDir, _ := createImageMetadata(c) 217 stor, err := filestorage.NewFileStorageWriter(metadataDir) 218 c.Assert(err, jc.ErrorIsNil) 219 envtesting.UploadFakeTools(c, stor, "released", "released") 220 221 env := bootstrapEnvironWithRegion{ 222 newEnviron("foo", useDefaultKeys, nil), 223 simplestreams.CloudSpec{ 224 Region: "region", 225 Endpoint: "endpoint", 226 }, 227 } 228 s.setDummyStorage(c, env.bootstrapEnviron) 229 230 bootstrapCons := constraints.MustParse("arch=amd64") 231 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 232 BootstrapConstraints: bootstrapCons, 233 MetadataDir: metadataDir, 234 }) 235 c.Assert(err, jc.ErrorIsNil) 236 237 datasources, err := environs.ImageMetadataSources(env) 238 c.Assert(err, jc.ErrorIsNil) 239 for _, source := range datasources { 240 // make sure we looked in each and all... 241 c.Assert(c.GetTestLog(), jc.Contains, fmt.Sprintf("image metadata in %s", source.Description())) 242 } 243 } 244 245 func (s *bootstrapSuite) TestBootstrapNoToolsNonReleaseStream(c *gc.C) { 246 if runtime.GOOS == "windows" { 247 c.Skip("issue 1403084: Currently does not work because of jujud problems") 248 } 249 250 s.PatchValue(&arch.HostArch, func() string { return arch.ARM64 }) 251 s.PatchValue(bootstrap.FindTools, func(environs.Environ, int, int, string, tools.Filter) (tools.List, error) { 252 return nil, errors.NotFoundf("tools") 253 }) 254 env := newEnviron("foo", useDefaultKeys, map[string]interface{}{ 255 "agent-stream": "proposed"}) 256 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 257 BuildToolsTarball: func(*version.Number, string) (*sync.BuiltTools, error) { 258 return &sync.BuiltTools{Dir: c.MkDir()}, nil 259 }, 260 }) 261 // bootstrap.Bootstrap leaves it to the provider to 262 // locate bootstrap tools. 263 c.Assert(err, jc.ErrorIsNil) 264 } 265 266 func (s *bootstrapSuite) TestBootstrapNoToolsDevelopmentConfig(c *gc.C) { 267 if runtime.GOOS == "windows" { 268 c.Skip("issue 1403084: Currently does not work because of jujud problems") 269 } 270 271 s.PatchValue(&arch.HostArch, func() string { return arch.ARM64 }) 272 s.PatchValue(bootstrap.FindTools, func(environs.Environ, int, int, string, tools.Filter) (tools.List, error) { 273 return nil, errors.NotFoundf("tools") 274 }) 275 env := newEnviron("foo", useDefaultKeys, map[string]interface{}{ 276 "development": true}) 277 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 278 BuildToolsTarball: func(*version.Number, string) (*sync.BuiltTools, error) { 279 return &sync.BuiltTools{Dir: c.MkDir()}, nil 280 }, 281 }) 282 // bootstrap.Bootstrap leaves it to the provider to 283 // locate bootstrap tools. 284 c.Assert(err, jc.ErrorIsNil) 285 } 286 287 func (s *bootstrapSuite) TestSetBootstrapTools(c *gc.C) { 288 availableVersions := []version.Binary{ 289 version.MustParseBinary("1.18.0-trusty-arm64"), 290 version.MustParseBinary("1.18.1-trusty-arm64"), 291 version.MustParseBinary("1.18.1.1-trusty-arm64"), 292 version.MustParseBinary("1.18.1.2-trusty-arm64"), 293 version.MustParseBinary("1.18.1.3-trusty-arm64"), 294 } 295 availableTools := make(tools.List, len(availableVersions)) 296 for i, v := range availableVersions { 297 availableTools[i] = &tools.Tools{Version: v} 298 } 299 300 type test struct { 301 currentVersion version.Number 302 expectedTools version.Number 303 expectedAgentVersion version.Number 304 } 305 tests := []test{{ 306 currentVersion: version.MustParse("1.18.0"), 307 expectedTools: version.MustParse("1.18.0"), 308 expectedAgentVersion: version.MustParse("1.18.1.3"), 309 }, { 310 currentVersion: version.MustParse("1.18.1.4"), 311 expectedTools: version.MustParse("1.18.1.3"), 312 expectedAgentVersion: version.MustParse("1.18.1.3"), 313 }, { 314 // build number is ignored unless major/minor don't 315 // match the latest. 316 currentVersion: version.MustParse("1.18.1.2"), 317 expectedTools: version.MustParse("1.18.1.3"), 318 expectedAgentVersion: version.MustParse("1.18.1.3"), 319 }, { 320 // If the current patch level exceeds whatever's in 321 // the tools source (e.g. when bootstrapping from trunk) 322 // then the latest available tools will be chosen. 323 currentVersion: version.MustParse("1.18.2"), 324 expectedTools: version.MustParse("1.18.1.3"), 325 expectedAgentVersion: version.MustParse("1.18.1.3"), 326 }} 327 328 env := newEnviron("foo", useDefaultKeys, nil) 329 for i, t := range tests { 330 c.Logf("test %d: %+v", i, t) 331 cfg, err := env.Config().Remove([]string{"agent-version"}) 332 c.Assert(err, jc.ErrorIsNil) 333 err = env.SetConfig(cfg) 334 c.Assert(err, jc.ErrorIsNil) 335 s.PatchValue(&jujuversion.Current, t.currentVersion) 336 bootstrapTools, err := bootstrap.SetBootstrapTools(env, availableTools) 337 c.Assert(err, jc.ErrorIsNil) 338 for _, tools := range bootstrapTools { 339 c.Assert(tools.Version.Number, gc.Equals, t.expectedTools) 340 } 341 agentVersion, _ := env.Config().AgentVersion() 342 c.Assert(agentVersion, gc.Equals, t.expectedAgentVersion) 343 } 344 } 345 346 func (s *bootstrapSuite) TestBootstrapGUISuccessRemote(c *gc.C) { 347 s.PatchValue(bootstrap.GUIFetchMetadata, func(stream string, sources ...simplestreams.DataSource) ([]*gui.Metadata, error) { 348 c.Assert(stream, gc.Equals, gui.ReleasedStream) 349 c.Assert(sources[0].Description(), gc.Equals, "gui simplestreams") 350 c.Assert(sources[0].RequireSigned(), jc.IsTrue) 351 return []*gui.Metadata{{ 352 Version: version.MustParse("2.0.42"), 353 FullPath: "https://1.2.3.4/juju-gui-2.0.42.tar.bz2", 354 SHA256: "hash-2.0.42", 355 Size: 42, 356 }, { 357 Version: version.MustParse("2.0.47"), 358 FullPath: "https://1.2.3.4/juju-gui-2.0.47.tar.bz2", 359 SHA256: "hash-2.0.47", 360 Size: 47, 361 }}, nil 362 }) 363 env := newEnviron("foo", useDefaultKeys, nil) 364 ctx := coretesting.Context(c) 365 err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{ 366 GUIDataSourceBaseURL: "https://1.2.3.4/gui/sources", 367 }) 368 c.Assert(err, jc.ErrorIsNil) 369 c.Assert(coretesting.Stderr(ctx), jc.Contains, "Preparing for Juju GUI 2.0.42 release installation\n") 370 371 // The most recent GUI release info has been stored. 372 c.Assert(env.instanceConfig.GUI.URL, gc.Equals, "https://1.2.3.4/juju-gui-2.0.42.tar.bz2") 373 c.Assert(env.instanceConfig.GUI.Version.String(), gc.Equals, "2.0.42") 374 c.Assert(env.instanceConfig.GUI.Size, gc.Equals, int64(42)) 375 c.Assert(env.instanceConfig.GUI.SHA256, gc.Equals, "hash-2.0.42") 376 } 377 378 func (s *bootstrapSuite) TestBootstrapGUISuccessLocal(c *gc.C) { 379 path := makeGUIArchive(c, "jujugui-2.2.0") 380 s.PatchEnvironment("JUJU_GUI", path) 381 env := newEnviron("foo", useDefaultKeys, nil) 382 ctx := coretesting.Context(c) 383 err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) 384 c.Assert(err, jc.ErrorIsNil) 385 c.Assert(coretesting.Stderr(ctx), jc.Contains, "Preparing for Juju GUI 2.2.0 installation from local archive\n") 386 387 // Check GUI URL and version. 388 c.Assert(env.instanceConfig.GUI.URL, gc.Equals, "file://"+path) 389 c.Assert(env.instanceConfig.GUI.Version.String(), gc.Equals, "2.2.0") 390 391 // Check GUI size. 392 f, err := os.Open(path) 393 c.Assert(err, jc.ErrorIsNil) 394 defer f.Close() 395 info, err := f.Stat() 396 c.Assert(err, jc.ErrorIsNil) 397 c.Assert(env.instanceConfig.GUI.Size, gc.Equals, info.Size()) 398 399 // Check GUI hash. 400 h := sha256.New() 401 _, err = io.Copy(h, f) 402 c.Assert(err, jc.ErrorIsNil) 403 c.Assert(env.instanceConfig.GUI.SHA256, gc.Equals, fmt.Sprintf("%x", h.Sum(nil))) 404 } 405 406 func (s *bootstrapSuite) TestBootstrapGUISuccessNoGUI(c *gc.C) { 407 env := newEnviron("foo", useDefaultKeys, nil) 408 ctx := coretesting.Context(c) 409 err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) 410 c.Assert(err, jc.ErrorIsNil) 411 c.Assert(coretesting.Stderr(ctx), jc.Contains, "Juju GUI installation has been disabled\n") 412 c.Assert(env.instanceConfig.GUI, gc.IsNil) 413 } 414 415 func (s *bootstrapSuite) TestBootstrapGUINoStreams(c *gc.C) { 416 s.PatchValue(bootstrap.GUIFetchMetadata, func(string, ...simplestreams.DataSource) ([]*gui.Metadata, error) { 417 return nil, nil 418 }) 419 env := newEnviron("foo", useDefaultKeys, nil) 420 ctx := coretesting.Context(c) 421 err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{ 422 GUIDataSourceBaseURL: "https://1.2.3.4/gui/sources", 423 }) 424 c.Assert(err, jc.ErrorIsNil) 425 c.Assert(coretesting.Stderr(ctx), jc.Contains, "No available Juju GUI archives found\n") 426 c.Assert(env.instanceConfig.GUI, gc.IsNil) 427 } 428 429 func (s *bootstrapSuite) TestBootstrapGUIStreamsFailure(c *gc.C) { 430 s.PatchValue(bootstrap.GUIFetchMetadata, func(string, ...simplestreams.DataSource) ([]*gui.Metadata, error) { 431 return nil, errors.New("bad wolf") 432 }) 433 env := newEnviron("foo", useDefaultKeys, nil) 434 ctx := coretesting.Context(c) 435 err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{ 436 GUIDataSourceBaseURL: "https://1.2.3.4/gui/sources", 437 }) 438 c.Assert(err, jc.ErrorIsNil) 439 c.Assert(coretesting.Stderr(ctx), jc.Contains, "Unable to fetch Juju GUI info: bad wolf\n") 440 c.Assert(env.instanceConfig.GUI, gc.IsNil) 441 } 442 443 func (s *bootstrapSuite) TestBootstrapGUIErrorNotFound(c *gc.C) { 444 s.PatchEnvironment("JUJU_GUI", "/no/such/file") 445 env := newEnviron("foo", useDefaultKeys, nil) 446 ctx := coretesting.Context(c) 447 err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) 448 c.Assert(err, jc.ErrorIsNil) 449 c.Assert(coretesting.Stderr(ctx), jc.Contains, `Cannot use Juju GUI at "/no/such/file": cannot open Juju GUI archive:`) 450 } 451 452 func (s *bootstrapSuite) TestBootstrapGUIErrorInvalidArchive(c *gc.C) { 453 path := filepath.Join(c.MkDir(), "gui.bz2") 454 err := ioutil.WriteFile(path, []byte("invalid"), 0666) 455 c.Assert(err, jc.ErrorIsNil) 456 s.PatchEnvironment("JUJU_GUI", path) 457 env := newEnviron("foo", useDefaultKeys, nil) 458 ctx := coretesting.Context(c) 459 err = bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) 460 c.Assert(err, jc.ErrorIsNil) 461 c.Assert(coretesting.Stderr(ctx), jc.Contains, fmt.Sprintf("Cannot use Juju GUI at %q: cannot read Juju GUI archive", path)) 462 } 463 464 func (s *bootstrapSuite) TestBootstrapGUIErrorInvalidVersion(c *gc.C) { 465 path := makeGUIArchive(c, "jujugui-invalid") 466 s.PatchEnvironment("JUJU_GUI", path) 467 env := newEnviron("foo", useDefaultKeys, nil) 468 ctx := coretesting.Context(c) 469 err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) 470 c.Assert(err, jc.ErrorIsNil) 471 c.Assert(coretesting.Stderr(ctx), jc.Contains, fmt.Sprintf(`Cannot use Juju GUI at %q: cannot parse version "invalid"`, path)) 472 } 473 474 func (s *bootstrapSuite) TestBootstrapGUIErrorUnexpectedArchive(c *gc.C) { 475 path := makeGUIArchive(c, "not-a-gui") 476 s.PatchEnvironment("JUJU_GUI", path) 477 env := newEnviron("foo", useDefaultKeys, nil) 478 ctx := coretesting.Context(c) 479 err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) 480 c.Assert(err, jc.ErrorIsNil) 481 c.Assert(coretesting.Stderr(ctx), jc.Contains, fmt.Sprintf("Cannot use Juju GUI at %q: cannot find Juju GUI version", path)) 482 } 483 484 func makeGUIArchive(c *gc.C, dir string) string { 485 if runtime.GOOS == "windows" { 486 c.Skip("tar command not available") 487 } 488 target := filepath.Join(c.MkDir(), "gui.tar.bz2") 489 source := c.MkDir() 490 err := os.Mkdir(filepath.Join(source, dir), 0777) 491 c.Assert(err, jc.ErrorIsNil) 492 err = exec.Command("tar", "cjf", target, "-C", source, dir).Run() 493 c.Assert(err, jc.ErrorIsNil) 494 return target 495 } 496 497 // createImageMetadata creates some image metadata in a local directory. 498 func createImageMetadata(c *gc.C) (dir string, _ []*imagemetadata.ImageMetadata) { 499 // Generate some image metadata. 500 im := []*imagemetadata.ImageMetadata{{ 501 Id: "1234", 502 Arch: "amd64", 503 Version: "13.04", 504 RegionName: "region", 505 Endpoint: "endpoint", 506 }} 507 cloudSpec := &simplestreams.CloudSpec{ 508 Region: "region", 509 Endpoint: "endpoint", 510 } 511 sourceDir := c.MkDir() 512 sourceStor, err := filestorage.NewFileStorageWriter(sourceDir) 513 c.Assert(err, jc.ErrorIsNil) 514 err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor) 515 c.Assert(err, jc.ErrorIsNil) 516 return sourceDir, im 517 } 518 519 func (s *bootstrapSuite) TestBootstrapMetadata(c *gc.C) { 520 environs.UnregisterImageDataSourceFunc("bootstrap metadata") 521 522 metadataDir, metadata := createImageMetadata(c) 523 stor, err := filestorage.NewFileStorageWriter(metadataDir) 524 c.Assert(err, jc.ErrorIsNil) 525 envtesting.UploadFakeTools(c, stor, "released", "released") 526 527 env := newEnviron("foo", useDefaultKeys, nil) 528 s.setDummyStorage(c, env) 529 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 530 MetadataDir: metadataDir, 531 }) 532 c.Assert(err, jc.ErrorIsNil) 533 c.Assert(env.bootstrapCount, gc.Equals, 1) 534 c.Assert(envtools.DefaultBaseURL, gc.Equals, metadataDir) 535 536 datasources, err := environs.ImageMetadataSources(env) 537 c.Assert(err, jc.ErrorIsNil) 538 c.Assert(datasources, gc.HasLen, 3) 539 c.Assert(datasources[0].Description(), gc.Equals, "bootstrap metadata") 540 // This data source does not require to contain signed data. 541 // However, it may still contain it. 542 // Since we will always try to read signed data first, 543 // we want to be able to try to read this signed data 544 // with a user provided key. 545 // for this test, user provided key is empty. 546 // Bugs #1542127, #1542131 547 c.Assert(datasources[0].PublicSigningKey(), gc.Equals, "") 548 c.Assert(env.instanceConfig, gc.NotNil) 549 c.Assert(env.instanceConfig.CustomImageMetadata, gc.HasLen, 1) 550 c.Assert(env.instanceConfig.CustomImageMetadata[0], gc.DeepEquals, metadata[0]) 551 } 552 553 func (s *bootstrapSuite) TestPublicKeyEnvVar(c *gc.C) { 554 path := filepath.Join(c.MkDir(), "key") 555 ioutil.WriteFile(path, []byte("publickey"), 0644) 556 s.PatchEnvironment("JUJU_STREAMS_PUBLICKEY_FILE", path) 557 558 env := newEnviron("foo", useDefaultKeys, nil) 559 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 560 c.Assert(err, jc.ErrorIsNil) 561 c.Assert(env.instanceConfig.PublicImageSigningKey, gc.Equals, "publickey") 562 } 563 564 func (s *bootstrapSuite) TestBootstrapMetadataImagesMissing(c *gc.C) { 565 environs.UnregisterImageDataSourceFunc("bootstrap metadata") 566 567 noImagesDir := c.MkDir() 568 stor, err := filestorage.NewFileStorageWriter(noImagesDir) 569 c.Assert(err, jc.ErrorIsNil) 570 envtesting.UploadFakeTools(c, stor, "released", "released") 571 572 env := newEnviron("foo", useDefaultKeys, nil) 573 s.setDummyStorage(c, env) 574 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 575 MetadataDir: noImagesDir, 576 }) 577 c.Assert(err, jc.ErrorIsNil) 578 c.Assert(env.bootstrapCount, gc.Equals, 1) 579 580 datasources, err := environs.ImageMetadataSources(env) 581 c.Assert(err, jc.ErrorIsNil) 582 c.Assert(datasources, gc.HasLen, 2) 583 c.Assert(datasources[0].Description(), gc.Equals, "default cloud images") 584 c.Assert(datasources[1].Description(), gc.Equals, "default ubuntu cloud images") 585 } 586 587 func (s *bootstrapSuite) setupBootstrapSpecificVersion(c *gc.C, clientMajor, clientMinor int, toolsVersion *version.Number) (error, int, version.Number) { 588 currentVersion := jujuversion.Current 589 currentVersion.Major = clientMajor 590 currentVersion.Minor = clientMinor 591 currentVersion.Tag = "" 592 s.PatchValue(&jujuversion.Current, currentVersion) 593 s.PatchValue(&series.HostSeries, func() string { return "trusty" }) 594 s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 }) 595 596 env := newEnviron("foo", useDefaultKeys, nil) 597 s.setDummyStorage(c, env) 598 envtools.RegisterToolsDataSourceFunc("local storage", func(environs.Environ) (simplestreams.DataSource, error) { 599 return storage.NewStorageSimpleStreamsDataSource("test datasource", env.storage, "tools", simplestreams.CUSTOM_CLOUD_DATA, false), nil 600 }) 601 defer envtools.UnregisterToolsDataSourceFunc("local storage") 602 603 toolsBinaries := []version.Binary{ 604 version.MustParseBinary("10.11.12-trusty-amd64"), 605 version.MustParseBinary("10.11.13-trusty-amd64"), 606 version.MustParseBinary("10.11-beta1-trusty-amd64"), 607 } 608 stream := "released" 609 if toolsVersion != nil && toolsVersion.Tag != "" { 610 stream = "devel" 611 currentVersion.Tag = toolsVersion.Tag 612 } 613 _, err := envtesting.UploadFakeToolsVersions(env.storage, stream, stream, toolsBinaries...) 614 c.Assert(err, jc.ErrorIsNil) 615 616 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 617 AgentVersion: toolsVersion, 618 }) 619 vers, _ := env.cfg.AgentVersion() 620 return err, env.bootstrapCount, vers 621 } 622 623 func (s *bootstrapSuite) TestBootstrapSpecificVersion(c *gc.C) { 624 toolsVersion := version.MustParse("10.11.12") 625 err, bootstrapCount, vers := s.setupBootstrapSpecificVersion(c, 10, 11, &toolsVersion) 626 c.Assert(err, jc.ErrorIsNil) 627 c.Assert(bootstrapCount, gc.Equals, 1) 628 c.Assert(vers, gc.DeepEquals, version.Number{ 629 Major: 10, 630 Minor: 11, 631 Patch: 12, 632 }) 633 } 634 635 func (s *bootstrapSuite) TestBootstrapSpecificVersionWithTag(c *gc.C) { 636 toolsVersion := version.MustParse("10.11-beta1") 637 err, bootstrapCount, vers := s.setupBootstrapSpecificVersion(c, 10, 11, &toolsVersion) 638 c.Assert(err, jc.ErrorIsNil) 639 c.Assert(bootstrapCount, gc.Equals, 1) 640 c.Assert(vers, gc.DeepEquals, version.Number{ 641 Major: 10, 642 Minor: 11, 643 Patch: 1, 644 Tag: "beta", 645 }) 646 } 647 648 func (s *bootstrapSuite) TestBootstrapNoSpecificVersion(c *gc.C) { 649 // bootstrap with no specific version will use latest major.minor tools version. 650 err, bootstrapCount, vers := s.setupBootstrapSpecificVersion(c, 10, 11, nil) 651 c.Assert(err, jc.ErrorIsNil) 652 c.Assert(bootstrapCount, gc.Equals, 1) 653 c.Assert(vers, gc.DeepEquals, version.Number{ 654 Major: 10, 655 Minor: 11, 656 Patch: 13, 657 }) 658 } 659 660 func (s *bootstrapSuite) TestBootstrapSpecificVersionClientMinorMismatch(c *gc.C) { 661 // bootstrap using a specified version only works if the patch number is different. 662 // The bootstrap client major and minor versions need to match the tools asked for. 663 toolsVersion := version.MustParse("10.11.12") 664 err, bootstrapCount, _ := s.setupBootstrapSpecificVersion(c, 10, 1, &toolsVersion) 665 c.Assert(strings.Replace(err.Error(), "\n", "", -1), gc.Matches, ".* no tools are available .*") 666 c.Assert(bootstrapCount, gc.Equals, 0) 667 } 668 669 func (s *bootstrapSuite) TestBootstrapSpecificVersionClientMajorMismatch(c *gc.C) { 670 // bootstrap using a specified version only works if the patch number is different. 671 // The bootstrap client major and minor versions need to match the tools asked for. 672 toolsVersion := version.MustParse("10.11.12") 673 err, bootstrapCount, _ := s.setupBootstrapSpecificVersion(c, 1, 11, &toolsVersion) 674 c.Assert(strings.Replace(err.Error(), "\n", "", -1), gc.Matches, ".* no tools are available .*") 675 c.Assert(bootstrapCount, gc.Equals, 0) 676 } 677 678 type bootstrapEnviron struct { 679 cfg *config.Config 680 environs.Environ // stub out all methods we don't care about. 681 682 // The following fields are filled in when Bootstrap is called. 683 bootstrapCount int 684 finalizerCount int 685 supportedArchitecturesCount int 686 args environs.BootstrapParams 687 instanceConfig *instancecfg.InstanceConfig 688 storage storage.Storage 689 } 690 691 func newEnviron(name string, defaultKeys bool, extraAttrs map[string]interface{}) *bootstrapEnviron { 692 m := dummy.SampleConfig().Merge(extraAttrs) 693 if !defaultKeys { 694 m = m.Delete( 695 "ca-cert", 696 "ca-private-key", 697 "admin-secret", 698 ) 699 } 700 m["name"] = name // overwrite name provided by dummy.SampleConfig 701 cfg, err := config.New(config.NoDefaults, m) 702 if err != nil { 703 panic(fmt.Errorf("cannot create config from %#v: %v", m, err)) 704 } 705 return &bootstrapEnviron{ 706 cfg: cfg, 707 } 708 } 709 710 // setDummyStorage injects the local provider's fake storage implementation 711 // into the given environment, so that tests can manipulate storage as if it 712 // were real. 713 func (s *bootstrapSuite) setDummyStorage(c *gc.C, env *bootstrapEnviron) { 714 closer, stor, _ := envtesting.CreateLocalTestStorage(c) 715 env.storage = stor 716 s.AddCleanup(func(c *gc.C) { closer.Close() }) 717 } 718 719 func (e *bootstrapEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) { 720 e.bootstrapCount++ 721 e.args = args 722 finalizer := func(_ environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { 723 e.finalizerCount++ 724 e.instanceConfig = icfg 725 return nil 726 } 727 series := series.HostSeries() 728 if args.BootstrapSeries != "" { 729 series = args.BootstrapSeries 730 } 731 return &environs.BootstrapResult{Arch: arch.HostArch(), Series: series, Finalize: finalizer}, nil 732 } 733 734 func (e *bootstrapEnviron) Config() *config.Config { 735 return e.cfg 736 } 737 738 func (e *bootstrapEnviron) SetConfig(cfg *config.Config) error { 739 e.cfg = cfg 740 return nil 741 } 742 743 func (e *bootstrapEnviron) Storage() storage.Storage { 744 return e.storage 745 } 746 747 func (e *bootstrapEnviron) SupportedArchitectures() ([]string, error) { 748 e.supportedArchitecturesCount++ 749 return []string{arch.AMD64, arch.ARM64}, nil 750 } 751 752 func (e *bootstrapEnviron) ConstraintsValidator() (constraints.Validator, error) { 753 return constraints.NewValidator(), nil 754 } 755 756 type bootstrapEnvironWithRegion struct { 757 *bootstrapEnviron 758 region simplestreams.CloudSpec 759 } 760 761 func (e bootstrapEnvironWithRegion) Region() (simplestreams.CloudSpec, error) { 762 return e.region, nil 763 }