func nullContext() environs.BootstrapContext { ctx, _ := cmd.DefaultContext() ctx.Stdin = io.LimitReader(nil, 0) ctx.Stdout = ioutil.Discard ctx.Stderr = ioutil.Discard return modelcmd.BootstrapContext(ctx) }
// mockTestingEnvConfig prepares an environment configuration using // the mock provider which does not support networking. func mockTestingEnvConfig(c *gc.C) *config.Config { cfg, err := config.New(config.NoDefaults, mockConfig()) c.Assert(err, jc.ErrorIsNil) env, err := environs.Prepare(cfg, modelcmd.BootstrapContext(coretesting.Context(c)), configstore.NewMem()) c.Assert(err, jc.ErrorIsNil) return env.Config() }
func (s *bootstrapSuite) TestBootstrapGUISuccessLocal(c *gc.C) { path := makeGUIArchive(c, "jujugui-2.2.0") s.PatchEnvironment("JUJU_GUI", path) env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, "Preparing for Juju GUI 2.2.0 installation from local archive\n") // Check GUI URL and version. c.Assert(env.instanceConfig.GUI.URL, gc.Equals, "file://"+path) c.Assert(env.instanceConfig.GUI.Version.String(), gc.Equals, "2.2.0") // Check GUI size. f, err := os.Open(path) c.Assert(err, jc.ErrorIsNil) defer f.Close() info, err := f.Stat() c.Assert(err, jc.ErrorIsNil) c.Assert(env.instanceConfig.GUI.Size, gc.Equals, info.Size()) // Check GUI hash. h := sha256.New() _, err = io.Copy(h, f) c.Assert(err, jc.ErrorIsNil) c.Assert(env.instanceConfig.GUI.SHA256, gc.Equals, fmt.Sprintf("%x", h.Sum(nil))) }
func (s *bootstrapSuite) TestBootstrapGUISuccessRemote(c *gc.C) { s.PatchValue(bootstrap.GUIFetchMetadata, func(stream string, sources ...simplestreams.DataSource) ([]*gui.Metadata, error) { c.Assert(stream, gc.Equals, gui.ReleasedStream) c.Assert(sources[0].Description(), gc.Equals, "gui simplestreams") c.Assert(sources[0].RequireSigned(), jc.IsTrue) return []*gui.Metadata{{ Version: version.MustParse("2.0.42"), FullPath: "https://1.2.3.4/juju-gui-2.0.42.tar.bz2", SHA256: "hash-2.0.42", Size: 42, }, { Version: version.MustParse("2.0.47"), FullPath: "https://1.2.3.4/juju-gui-2.0.47.tar.bz2", SHA256: "hash-2.0.47", Size: 47, }}, nil }) env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{ GUIDataSourceBaseURL: "https://1.2.3.4/gui/sources", }) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, "Preparing for Juju GUI 2.0.42 release installation\n") // The most recent GUI release info has been stored. c.Assert(env.instanceConfig.GUI.URL, gc.Equals, "https://1.2.3.4/juju-gui-2.0.42.tar.bz2") c.Assert(env.instanceConfig.GUI.Version.String(), gc.Equals, "2.0.42") c.Assert(env.instanceConfig.GUI.Size, gc.Equals, int64(42)) c.Assert(env.instanceConfig.GUI.SHA256, gc.Equals, "hash-2.0.42") }
func (s *Suite) SetUpTest(c *gc.C) { // Set up InitialConfig with a dummy provider configuration. This // is required to allow model import test to work. env, err := environs.Prepare( modelcmd.BootstrapContext(testing.Context(c)), jujuclienttesting.NewMemStore(), environs.PrepareParams{ ControllerName: "dummycontroller", BaseConfig: dummy.SampleConfig(), CloudName: "dummy", }, ) c.Assert(err, jc.ErrorIsNil) s.InitialConfig = testing.CustomModelConfig(c, env.Config().AllAttrs()) // The call up to StateSuite's SetUpTest uses s.InitialConfig so // it has to happen here. s.StateSuite.SetUpTest(c) s.resources = common.NewResources() s.AddCleanup(func(*gc.C) { s.resources.StopAll() }) s.authorizer = apiservertesting.FakeAuthorizer{ Tag: s.Owner, } }
func (s *bootstrapSuite) TestBootstrapGUIErrorNotFound(c *gc.C) { s.PatchEnvironment("JUJU_GUI", "/no/such/file") env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, `Cannot use Juju GUI at "/no/such/file": cannot open Juju GUI archive:`) }
func (s *bootstrapSuite) TestBootstrapGUISuccessNoGUI(c *gc.C) { env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, "Juju GUI installation has been disabled\n") c.Assert(env.instanceConfig.GUI, gc.IsNil) }
func (s *bootstrapSuite) TestBootstrapGUIErrorUnexpectedArchive(c *gc.C) { path := makeGUIArchive(c, "not-a-gui") s.PatchEnvironment("JUJU_GUI", path) env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, fmt.Sprintf("Cannot use Juju GUI at %q: cannot find Juju GUI version", path)) }
func (s *bootstrapSuite) TestBootstrapGUIErrorInvalidArchive(c *gc.C) { path := filepath.Join(c.MkDir(), "gui.bz2") err := ioutil.WriteFile(path, []byte("invalid"), 0666) c.Assert(err, jc.ErrorIsNil) s.PatchEnvironment("JUJU_GUI", path) env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err = bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{}) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, fmt.Sprintf("Cannot use Juju GUI at %q: cannot read Juju GUI archive", path)) }
// resetJujuHome restores an new, clean Juju home environment without tools. func resetJujuHome(c *gc.C, envName string) environs.Environ { jenvDir := testing.HomePath(".juju", "models") err := os.RemoveAll(jenvDir) c.Assert(err, jc.ErrorIsNil) coretesting.WriteEnvironments(c, modelConfig) dummy.Reset() store, err := configstore.Default() c.Assert(err, jc.ErrorIsNil) env, err := environs.PrepareFromName(envName, modelcmd.BootstrapContext(cmdtesting.NullContext(c)), store) c.Assert(err, jc.ErrorIsNil) return env }
func (s *bootstrapSuite) TestBootstrapGUISuccessNoGUI(c *gc.C) { env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{ ControllerConfig: coretesting.FakeControllerConfig(), AdminSecret: "admin-secret", CAPrivateKey: coretesting.CAKey, }) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, "Juju GUI installation has been disabled\n") c.Assert(env.instanceConfig.Bootstrap.GUI, gc.IsNil) }
func testingEnvConfig(c *gc.C) *config.Config { env, err := environs.Prepare( modelcmd.BootstrapContext(testing.Context(c)), jujuclienttesting.NewMemStore(), environs.PrepareParams{ ControllerName: "dummycontroller", BaseConfig: dummy.SampleConfig(), CloudName: "dummy", }, ) c.Assert(err, jc.ErrorIsNil) return env.Config() }
func (s *bootstrapSuite) TestBootstrapGUIStreamsFailure(c *gc.C) { s.PatchValue(bootstrap.GUIFetchMetadata, func(string, ...simplestreams.DataSource) ([]*gui.Metadata, error) { return nil, errors.New("bad wolf") }) env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{ GUIDataSourceBaseURL: "https://1.2.3.4/gui/sources", }) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, "Unable to fetch Juju GUI info: bad wolf\n") c.Assert(env.instanceConfig.GUI, gc.IsNil) }
func (s *bootstrapSuite) TestBootstrapGUIErrorInvalidVersion(c *gc.C) { path := makeGUIArchive(c, "jujugui-invalid") s.PatchEnvironment("JUJU_GUI", path) env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{ ControllerConfig: coretesting.FakeControllerConfig(), AdminSecret: "admin-secret", CAPrivateKey: coretesting.CAKey, }) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, fmt.Sprintf(`Cannot use Juju GUI at %q: cannot parse version "invalid"`, path)) }
// rebootstrap will bootstrap a new server in safe-mode (not killing any other agent) // if there is no current server available to restore to. func (c *restoreCommand) rebootstrap(ctx *cmd.Context, meta *params.BackupsMetadataResult) error { env, err := c.getEnvironFunc(c.ControllerName(), meta) if err != nil { return errors.Trace(err) } instanceIds, err := env.ControllerInstances() if err != nil && errors.Cause(err) != environs.ErrNotBootstrapped { return errors.Annotatef(err, "cannot determine controller instances") } if len(instanceIds) > 0 { inst, err := env.Instances(instanceIds) if err == nil { return errors.Errorf("old bootstrap instance %q still seems to exist; will not replace", inst) } if err != environs.ErrNoInstances { return errors.Annotatef(err, "cannot detect whether old instance is still running") } } // We require a hosted model config to bootstrap. We'll fill in some defaults // just to get going. The restore will clear the initial state. hostedModelUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } hostedModelConfig := map[string]interface{}{ "name": "default", config.UUIDKey: hostedModelUUID.String(), } args := bootstrap.BootstrapParams{ ModelConstraints: c.constraints, UploadTools: c.uploadTools, BuildToolsTarball: sync.BuildToolsTarball, HostedModelConfig: hostedModelConfig, } if err := BootstrapFunc(modelcmd.BootstrapContext(ctx), env, args); err != nil { return errors.Annotatef(err, "cannot bootstrap new instance") } // New controller is bootstrapped, so now record the API address so // we can connect. err = common.SetBootstrapEndpointAddress(c.ClientStore(), c.ControllerName(), env) if err != nil { errors.Trace(err) } // To avoid race conditions when running scripted bootstraps, wait // for the controller's machine agent to be ready to accept commands // before exiting this bootstrap command. return c.waitForAgentFunc(ctx, &c.ModelCommandBase, c.ControllerName()) }
func (s *BootstrapSuite) TestSuccess(c *gc.C) { s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) stor := newStorage(s, c) checkInstanceId := "i-success" checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T") startInstance := func( _ string, _ constraints.Value, _ []string, _ tools.List, icfg *instancecfg.InstanceConfig, ) ( instance.Instance, *instance.HardwareCharacteristics, []network.InterfaceInfo, error, ) { return &mockInstance{id: checkInstanceId}, &checkHardware, nil, nil } var mocksConfig = minimalConfig(c) var getConfigCalled int getConfig := func() *config.Config { getConfigCalled++ return mocksConfig } setConfig := func(c *config.Config) error { mocksConfig = c return nil } env := &mockEnviron{ storage: stor, startInstance: startInstance, config: getConfig, setConfig: setConfig, } inner := coretesting.Context(c) ctx := modelcmd.BootstrapContext(inner) result, err := common.Bootstrap(ctx, env, environs.BootstrapParams{ ControllerConfig: coretesting.FakeControllerConfig(), AvailableTools: tools.List{ &tools.Tools{ Version: version.Binary{ Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), }, }, }}) c.Assert(err, jc.ErrorIsNil) c.Assert(result.Arch, gc.Equals, "ppc64el") // based on hardware characteristics c.Assert(result.Series, gc.Equals, config.PreferredSeries(mocksConfig)) output := inner.Stderr.(*bytes.Buffer) lines := strings.Split(output.String(), "\n") c.Assert(len(lines), jc.GreaterThan, 1) c.Assert(lines[0], gc.Equals, "Some message") }
func testingEnvConfig(c *gc.C) *config.Config { env, err := bootstrap.Prepare( modelcmd.BootstrapContext(testing.Context(c)), jujuclienttesting.NewMemStore(), bootstrap.PrepareParams{ ControllerConfig: testing.FakeControllerConfig(), ControllerName: "dummycontroller", ModelConfig: dummy.SampleConfig(), Cloud: dummy.SampleCloudSpec(), AdminSecret: "admin-secret", }, ) c.Assert(err, jc.ErrorIsNil) return env.Config() }
func (s *bootstrapSuite) TestBootstrapGUINoStreams(c *gc.C) { s.PatchValue(bootstrap.GUIFetchMetadata, func(string, ...simplestreams.DataSource) ([]*gui.Metadata, error) { return nil, nil }) env := newEnviron("foo", useDefaultKeys, nil) ctx := coretesting.Context(c) err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{ ControllerConfig: coretesting.FakeControllerConfig(), AdminSecret: "admin-secret", CAPrivateKey: coretesting.CAKey, GUIDataSourceBaseURL: "https://1.2.3.4/gui/sources", }) c.Assert(err, jc.ErrorIsNil) c.Assert(coretesting.Stderr(ctx), jc.Contains, "No available Juju GUI archives found\n") c.Assert(env.instanceConfig.Bootstrap.GUI, gc.IsNil) }
func (s *BootstrapSuite) TestBootstrapJenvWarning(c *gc.C) { const envName = "devenv" s.patchVersionAndSeries(c, envName) store, err := configstore.Default() c.Assert(err, jc.ErrorIsNil) ctx := coretesting.Context(c) environs.PrepareFromName(envName, modelcmd.BootstrapContext(ctx), store) logger := "jenv.warning.test" var testWriter loggo.TestWriter loggo.RegisterWriter(logger, &testWriter, loggo.WARNING) defer loggo.RemoveWriter(logger) _, errc := cmdtesting.RunCommand(ctx, newBootstrapCommand(), "-m", envName, "--auto-upgrade") c.Assert(<-errc, gc.IsNil) c.Assert(testWriter.Log(), jc.LogMatches, []string{"ignoring environments.yaml: using bootstrap config in .*"}) }
func environFromNameProductionFunc( ctx *cmd.Context, envName string, action string, ensureNotBootstrapped func(environs.Environ) error, ) (env environs.Environ, cleanup func(), err error) { store, err := configstore.Default() if err != nil { return nil, nil, err } envExisted := false if environInfo, err := store.ReadInfo(envName); err == nil { envExisted = true logger.Warningf( "ignoring environments.yaml: using bootstrap config in %s", environInfo.Location(), ) } else if !errors.IsNotFound(err) { return nil, nil, err } cleanup = func() { // Distinguish b/t removing the jenv file or tearing down the // environment. We want to remove the jenv file if preparation // was not successful. We want to tear down the environment // only in the case where the environment didn't already // exist. if env == nil { logger.Debugf("Destroying model info.") destroyEnvInfo(ctx, envName, store, action) } else if !envExisted && ensureNotBootstrapped(env) != environs.ErrAlreadyBootstrapped { logger.Debugf("Destroying model.") destroyPreparedEnviron(ctx, env, store, action) } } if env, err = environs.PrepareFromName(envName, modelcmd.BootstrapContext(ctx), store); err != nil { return nil, cleanup, err } return env, cleanup, err }
// rebootstrap will bootstrap a new server in safe-mode (not killing any other agent) // if there is no current server available to restore to. func (c *restoreCommand) rebootstrap(ctx *cmd.Context) error { store, err := configstore.Default() if err != nil { return errors.Trace(err) } cfg, err := c.Config(store, nil) if err != nil { return errors.Trace(err) } // Turn on safe mode so that the newly bootstrapped instance // will not destroy all the instances it does not know about. cfg, err = cfg.Apply(map[string]interface{}{ "provisioner-safe-mode": true, }) if err != nil { return errors.Annotatef(err, "cannot enable provisioner-safe-mode") } env, err := environs.New(cfg) if err != nil { return errors.Trace(err) } instanceIds, err := env.ControllerInstances() if err != nil { return errors.Annotatef(err, "cannot determine controller instances") } if len(instanceIds) == 0 { return errors.Errorf("no instances found; perhaps the model was not bootstrapped") } inst, err := env.Instances(instanceIds) if err == nil { return errors.Errorf("old bootstrap instance %q still seems to exist; will not replace", inst) } if err != environs.ErrNoInstances { return errors.Annotatef(err, "cannot detect whether old instance is still running") } cons := c.constraints args := bootstrap.BootstrapParams{EnvironConstraints: cons, UploadTools: c.uploadTools} if err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, args); err != nil { return errors.Annotatef(err, "cannot bootstrap new instance") } return nil }
func (s *ImportSuite) SetUpTest(c *gc.C) { // Specify the config to use for the controller model before calling // SetUpTest of the StateSuite, otherwise we get testing.ModelConfig(c). // The default provider type specified in the testing.ModelConfig function // is one that isn't registered as a valid provider. For our tests here we // need a real registered provider, so we use the dummy provider. // NOTE: make a better test provider. env, err := environs.Prepare( modelcmd.BootstrapContext(testing.Context(c)), jujuclienttesting.NewMemStore(), environs.PrepareParams{ ControllerName: "dummycontroller", BaseConfig: dummy.SampleConfig(), CloudName: "dummy", }, ) c.Assert(err, jc.ErrorIsNil) s.InitialConfig = testing.CustomModelConfig(c, env.Config().AllAttrs()) s.StateSuite.SetUpTest(c) }
// rebootstrap will bootstrap a new server in safe-mode (not killing any other agent) // if there is no current server available to restore to. func (c *restoreCommand) rebootstrap(ctx *cmd.Context, meta *params.BackupsMetadataResult) error { env, err := c.getEnvironFunc(c.ControllerName(), meta) if err != nil { return errors.Trace(err) } instanceIds, err := env.ControllerInstances() if err != nil && errors.Cause(err) != environs.ErrNotBootstrapped { return errors.Annotatef(err, "cannot determine controller instances") } if len(instanceIds) > 0 { inst, err := env.Instances(instanceIds) if err == nil { return errors.Errorf("old bootstrap instance %q still seems to exist; will not replace", inst) } if err != environs.ErrNoInstances { return errors.Annotatef(err, "cannot detect whether old instance is still running") } } // We require a hosted model config to bootstrap. We'll fill in some defaults // just to get going. The restore will clear the initial state. hostedModelUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } hostedModelConfig := map[string]interface{}{ "name": "default", config.UUIDKey: hostedModelUUID.String(), } args := bootstrap.BootstrapParams{ ModelConstraints: c.constraints, UploadTools: c.uploadTools, HostedModelConfig: hostedModelConfig, } if err := BootstrapFunc(modelcmd.BootstrapContext(ctx), env, args); err != nil { return errors.Annotatef(err, "cannot bootstrap new instance") } return nil }
func (s *imageMetadataUpdateSuite) TestUpdateFromPublishedImagesForProviderWithNoRegions(c *gc.C) { // This will save all available image metadata. saved := []cloudimagemetadata.Metadata{} // testingEnvConfig prepares an environment configuration using // the dummy provider since it doesn't implement simplestreams.HasRegion. s.state.environConfig = func() (*config.Config, error) { cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) c.Assert(err, jc.ErrorIsNil) env, err := environs.Prepare(cfg, modelcmd.BootstrapContext(testing.Context(c)), configstore.NewMem()) c.Assert(err, jc.ErrorIsNil) return env.Config(), err } s.state.saveMetadata = func(m []cloudimagemetadata.Metadata) error { saved = append(saved, m...) return nil } err := s.api.UpdateFromPublishedImages() c.Assert(err, jc.ErrorIsNil) s.assertCalls(c, environConfig) c.Assert(saved, jc.SameContents, []cloudimagemetadata.Metadata{}) }
func (s *ModelCommandSuite) TestBootstrapContext(c *gc.C) { ctx := modelcmd.BootstrapContext(&cmd.Context{}) c.Assert(ctx.ShouldVerifyCredentials(), jc.IsTrue) }
// BootstrapContext creates a simple bootstrap execution context. func BootstrapContext(c *gc.C) environs.BootstrapContext { return modelcmd.BootstrapContext(coretesting.Context(c)) }
// Run connects to the environment specified on the command line and bootstraps // a juju in that environment if none already exists. If there is as yet no environments.yaml file, // the user is informed how to create one. func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { bootstrapFuncs := getBootstrapFuncs() envName := getModelName(c) if envName == "" { return errors.Errorf("the name of the model must be specified") } if err := checkProviderType(envName); errors.IsNotFound(err) { // This error will get handled later. } else if err != nil { return errors.Trace(err) } environ, cleanup, err := environFromName( ctx, envName, "Bootstrap", bootstrapFuncs.EnsureNotBootstrapped, ) // If we error out for any reason, clean up the environment. defer func() { if resultErr != nil && cleanup != nil { if c.KeepBrokenEnvironment { logger.Warningf("bootstrap failed but --keep-broken was specified so model is not being destroyed.\n" + "When you are finished diagnosing the problem, remember to run juju destroy-model --force\n" + "to clean up the model.") } else { handleBootstrapError(ctx, resultErr, cleanup) } } }() // Handle any errors from environFromName(...). if err != nil { return errors.Annotatef(err, "there was an issue examining the model") } // Check to see if this environment is already bootstrapped. If it // is, we inform the user and exit early. If an error is returned // but it is not that the environment is already bootstrapped, // then we're in an unknown state. if err := bootstrapFuncs.EnsureNotBootstrapped(environ); nil != err { if environs.ErrAlreadyBootstrapped == err { logger.Warningf("This juju model is already bootstrapped. If you want to start a new Juju\nmodel, first run juju destroy-model to clean up, or switch to an\nalternative model.") return err } return errors.Annotatef(err, "cannot determine if model is already bootstrapped.") } // Block interruption during bootstrap. Providers may also // register for interrupt notification so they can exit early. interrupted := make(chan os.Signal, 1) defer close(interrupted) ctx.InterruptNotify(interrupted) defer ctx.StopInterruptNotify(interrupted) go func() { for _ = range interrupted { ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") } }() // If --metadata-source is specified, override the default tools metadata source so // SyncTools can use it, and also upload any image metadata. var metadataDir string if c.MetadataSource != "" { metadataDir = ctx.AbsPath(c.MetadataSource) } // Merge environ and bootstrap-specific constraints. constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return errors.Trace(err) } bootstrapConstraints, err := constraintsValidator.Merge( c.Constraints, c.BootstrapConstraints, ) if err != nil { return errors.Trace(err) } logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints) err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ EnvironConstraints: c.Constraints, BootstrapConstraints: bootstrapConstraints, BootstrapSeries: c.BootstrapSeries, BootstrapImage: c.BootstrapImage, Placement: c.Placement, UploadTools: c.UploadTools, AgentVersion: c.AgentVersion, MetadataDir: metadataDir, }) if err != nil { return errors.Annotate(err, "failed to bootstrap model") } err = c.SetBootstrapEndpointAddress(environ) if err != nil { return errors.Annotate(err, "saving bootstrap endpoint address") } err = modelcmd.SetCurrentModel(ctx, envName) if err != nil { return errors.Trace(err) } // To avoid race conditions when running scripted bootstraps, wait // for the controller's machine agent to be ready to accept commands // before exiting this bootstrap command. return c.waitForAgentInitialisation(ctx) }
// Run connects to the environment specified on the command line and bootstraps // a juju in that environment if none already exists. If there is as yet no environments.yaml file, // the user is informed how to create one. func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { bootstrapFuncs := getBootstrapFuncs() // Get the cloud definition identified by c.Cloud. If c.Cloud does not // identify a cloud in clouds.yaml, but is the name of a provider, and // that provider implements environs.CloudRegionDetector, we'll // synthesise a Cloud structure with the detected regions and no auth- // types. cloud, err := jujucloud.CloudByName(c.Cloud) if errors.IsNotFound(err) { ctx.Verbosef("cloud %q not found, trying as a provider name", c.Cloud) provider, err := environs.Provider(c.Cloud) if errors.IsNotFound(err) { return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) } else if err != nil { return errors.Trace(err) } detector, ok := provider.(environs.CloudRegionDetector) if !ok { ctx.Verbosef( "provider %q does not support detecting regions", c.Cloud, ) return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) } regions, err := detector.DetectRegions() if err != nil && !errors.IsNotFound(err) { // It's not an error to have no regions. return errors.Annotatef(err, "detecting regions for %q cloud provider", c.Cloud, ) } cloud = &jujucloud.Cloud{ Type: c.Cloud, Regions: regions, } } else if err != nil { return errors.Trace(err) } if err := checkProviderType(cloud.Type); errors.IsNotFound(err) { // This error will get handled later. } else if err != nil { return errors.Trace(err) } // Get the credentials and region name. store := c.ClientStore() credential, credentialName, regionName, err := modelcmd.GetCredentials( store, c.Region, c.CredentialName, c.Cloud, cloud.Type, ) if errors.IsNotFound(err) && c.CredentialName == "" { // No credential was explicitly specified, and no credential // was found in credentials.yaml; have the provider detect // credentials from the environment. ctx.Verbosef("no credentials found, checking environment") detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type) if errors.Cause(err) == modelcmd.ErrMultipleCredentials { return ambiguousCredentialError } else if err != nil { return errors.Trace(err) } // We have one credential so extract it from the map. var oneCredential jujucloud.Credential for _, oneCredential = range detected.AuthCredentials { } credential = &oneCredential regionName = c.Region if regionName == "" { regionName = detected.DefaultRegion } logger.Tracef("authenticating with region %q and %v", regionName, credential) } else if err != nil { return errors.Trace(err) } region, err := getRegion(cloud, c.Cloud, regionName) if err != nil { return errors.Trace(err) } hostedModelUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } controllerUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } // Create an environment config from the cloud and credentials. configAttrs := map[string]interface{}{ "type": cloud.Type, "name": environs.ControllerModelName, config.UUIDKey: controllerUUID.String(), config.ControllerUUIDKey: controllerUUID.String(), } userConfigAttrs, err := c.config.ReadAttrs(ctx) if err != nil { return errors.Trace(err) } for k, v := range userConfigAttrs { configAttrs[k] = v } logger.Debugf("preparing controller with config: %v", configAttrs) // Read existing current controller, account, model so we can clean up on error. var oldCurrentController string oldCurrentController, err = modelcmd.ReadCurrentController() if err != nil { return errors.Annotate(err, "error reading current controller") } defer func() { if resultErr == nil || errors.IsAlreadyExists(resultErr) { return } if oldCurrentController != "" { if err := modelcmd.WriteCurrentController(oldCurrentController); err != nil { logger.Warningf( "cannot reset current controller to %q: %v", oldCurrentController, err, ) } } if err := store.RemoveController(c.controllerName); err != nil { logger.Warningf( "cannot destroy newly created controller %q details: %v", c.controllerName, err, ) } }() environ, err := environsPrepare( modelcmd.BootstrapContext(ctx), store, environs.PrepareParams{ BaseConfig: configAttrs, ControllerName: c.controllerName, CloudName: c.Cloud, CloudRegion: region.Name, CloudEndpoint: region.Endpoint, CloudStorageEndpoint: region.StorageEndpoint, Credential: *credential, CredentialName: credentialName, }, ) if err != nil { return errors.Trace(err) } // Set the current model to the initial hosted model. accountName, err := store.CurrentAccount(c.controllerName) if err != nil { return errors.Trace(err) } if err := store.UpdateModel(c.controllerName, accountName, c.hostedModelName, jujuclient.ModelDetails{ hostedModelUUID.String(), }); err != nil { return errors.Trace(err) } if err := store.SetCurrentModel(c.controllerName, accountName, c.hostedModelName); err != nil { return errors.Trace(err) } // Set the current controller so "juju status" can be run while // bootstrapping is underway. if err := modelcmd.WriteCurrentController(c.controllerName); err != nil { return errors.Trace(err) } cloudRegion := c.Cloud if region.Name != "" { cloudRegion = fmt.Sprintf("%s/%s", cloudRegion, region.Name) } ctx.Infof( "Creating Juju controller %q on %s", c.controllerName, cloudRegion, ) // If we error out for any reason, clean up the environment. defer func() { if resultErr != nil { if c.KeepBrokenEnvironment { logger.Warningf(` bootstrap failed but --keep-broken was specified so model is not being destroyed. When you are finished diagnosing the problem, remember to run juju destroy-model --force to clean up the model.`[1:]) } else { handleBootstrapError(ctx, resultErr, func() error { return environsDestroy( c.controllerName, environ, store, ) }) } } }() // Block interruption during bootstrap. Providers may also // register for interrupt notification so they can exit early. interrupted := make(chan os.Signal, 1) defer close(interrupted) ctx.InterruptNotify(interrupted) defer ctx.StopInterruptNotify(interrupted) go func() { for _ = range interrupted { ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") } }() // If --metadata-source is specified, override the default tools metadata source so // SyncTools can use it, and also upload any image metadata. var metadataDir string if c.MetadataSource != "" { metadataDir = ctx.AbsPath(c.MetadataSource) } // Merge environ and bootstrap-specific constraints. constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return errors.Trace(err) } bootstrapConstraints, err := constraintsValidator.Merge( c.Constraints, c.BootstrapConstraints, ) if err != nil { return errors.Trace(err) } logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints) hostedModelConfig := map[string]interface{}{ "name": c.hostedModelName, config.UUIDKey: hostedModelUUID.String(), } // We copy across any user supplied attributes to the hosted model config. // But only if the attributes have not been removed from the controller // model config as part of preparing the controller model. controllerConfigAttrs := environ.Config().AllAttrs() for k, v := range userConfigAttrs { if _, ok := controllerConfigAttrs[k]; ok { hostedModelConfig[k] = v } } // Ensure that certain config attributes are not included in the hosted // model config. These attributes may be modified during bootstrap; by // removing them from this map, we ensure the modified values are // inherited. delete(hostedModelConfig, config.AuthKeysConfig) delete(hostedModelConfig, config.AgentVersionKey) // Check whether the Juju GUI must be installed in the controller. // Leaving this value empty means no GUI will be installed. var guiDataSourceBaseURL string if !c.noGUI { guiDataSourceBaseURL = common.GUIDataSourceBaseURL() } err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ ModelConstraints: c.Constraints, BootstrapConstraints: bootstrapConstraints, BootstrapSeries: c.BootstrapSeries, BootstrapImage: c.BootstrapImage, Placement: c.Placement, UploadTools: c.UploadTools, AgentVersion: c.AgentVersion, MetadataDir: metadataDir, HostedModelConfig: hostedModelConfig, GUIDataSourceBaseURL: guiDataSourceBaseURL, }) if err != nil { return errors.Annotate(err, "failed to bootstrap model") } if err := c.SetModelName(c.hostedModelName); err != nil { return errors.Trace(err) } err = c.setBootstrapEndpointAddress(environ) if err != nil { return errors.Annotate(err, "saving bootstrap endpoint address") } // To avoid race conditions when running scripted bootstraps, wait // for the controller's machine agent to be ready to accept commands // before exiting this bootstrap command. return c.waitForAgentInitialisation(ctx) }
// rebootstrap will bootstrap a new server in safe-mode (not killing any other agent) // if there is no current server available to restore to. func (c *restoreCommand) rebootstrap(ctx *cmd.Context, meta *params.BackupsMetadataResult) error { params, err := c.getRebootstrapParamsFunc(ctx, c.ControllerName(), meta) if err != nil { return errors.Trace(err) } cloudParam, err := cloud.CloudByName(params.Cloud.Name) if errors.IsNotFound(err) { provider, err := environs.Provider(params.Cloud.Type) if errors.IsNotFound(err) { return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", params.Cloud.Name, "juju update-clouds")) } else if err != nil { return errors.Trace(err) } detector, ok := provider.(environs.CloudRegionDetector) if !ok { return errors.Errorf("provider %q does not support detecting regions", params.Cloud.Type) } var cloudEndpoint string regions, err := detector.DetectRegions() if errors.IsNotFound(err) { // It's not an error to have no regions. If the // provider does not support regions, then we // reinterpret the supplied region name as the // cloud's endpoint. This enables the user to // supply, for example, maas/<IP> or manual/<IP>. if params.Cloud.Region != "" { cloudEndpoint = params.Cloud.Region } } else if err != nil { return errors.Annotatef(err, "detecting regions for %q cloud provider", params.Cloud.Type) } schemas := provider.CredentialSchemas() authTypes := make([]cloud.AuthType, 0, len(schemas)) for authType := range schemas { authTypes = append(authTypes, authType) } cloudParam = &cloud.Cloud{ Type: params.Cloud.Type, AuthTypes: authTypes, Endpoint: cloudEndpoint, Regions: regions, } } else if err != nil { return errors.Trace(err) } env, err := c.newEnvironFunc(environs.OpenParams{ Cloud: params.Cloud, Config: params.ModelConfig, }) if err != nil { return errors.Annotate(err, "opening environ for rebootstrapping") } instanceIds, err := env.ControllerInstances(params.ControllerConfig.ControllerUUID()) if err != nil && errors.Cause(err) != environs.ErrNotBootstrapped { return errors.Annotatef(err, "cannot determine controller instances") } if len(instanceIds) > 0 { inst, err := env.Instances(instanceIds) if err == nil { return errors.Errorf("old bootstrap instance %q still seems to exist; will not replace", inst) } if err != environs.ErrNoInstances { return errors.Annotatef(err, "cannot detect whether old instance is still running") } } // We require a hosted model config to bootstrap. We'll fill in some defaults // just to get going. The restore will clear the initial state. hostedModelUUID, err := utils.NewUUID() if err != nil { return errors.Trace(err) } hostedModelConfig := map[string]interface{}{ "name": "default", config.UUIDKey: hostedModelUUID.String(), } // We may have previous controller metadata. We need to replace that so it // will contain the new CA Cert and UUID required to connect to the newly // bootstrapped controller API. store := c.ClientStore() details := jujuclient.ControllerDetails{ ControllerUUID: params.ControllerConfig.ControllerUUID(), CACert: meta.CACert, Cloud: params.Cloud.Name, CloudRegion: params.Cloud.Region, } err = store.UpdateController(c.ControllerName(), details) if err != nil { return errors.Trace(err) } bootVers := version.Current args := bootstrap.BootstrapParams{ Cloud: *cloudParam, CloudName: params.Cloud.Name, CloudRegion: params.Cloud.Region, CloudCredentialName: params.CredentialName, CloudCredential: params.Cloud.Credential, ModelConstraints: c.constraints, BuildAgent: c.buildAgent, BuildAgentTarball: sync.BuildAgentTarball, ControllerConfig: params.ControllerConfig, HostedModelConfig: hostedModelConfig, BootstrapSeries: meta.Series, AgentVersion: &bootVers, AdminSecret: params.AdminSecret, CAPrivateKey: meta.CAPrivateKey, DialOpts: environs.BootstrapDialOpts{ Timeout: time.Second * bootstrap.DefaultBootstrapSSHTimeout, RetryDelay: time.Second * bootstrap.DefaultBootstrapSSHRetryDelay, AddressesDelay: time.Second * bootstrap.DefaultBootstrapSSHAddressesDelay, }, } if err := BootstrapFunc(modelcmd.BootstrapContext(ctx), env, args); err != nil { return errors.Annotatef(err, "cannot bootstrap new instance") } // New controller is bootstrapped, so now record the API address so // we can connect. apiPort := params.ControllerConfig.APIPort() err = common.SetBootstrapEndpointAddress(store, c.ControllerName(), bootVers, apiPort, env) if err != nil { return errors.Trace(err) } // To avoid race conditions when running scripted bootstraps, wait // for the controller's machine agent to be ready to accept commands // before exiting this bootstrap command. return c.waitForAgentFunc(ctx, &c.ModelCommandBase, c.ControllerName(), "default") }
func (s *JujuConnSuite) setUpConn(c *gc.C) { if s.RootDir != "" { c.Fatal("JujuConnSuite.setUpConn without teardown") } s.RootDir = c.MkDir() s.oldHome = utils.Home() home := filepath.Join(s.RootDir, "/home/ubuntu") err := os.MkdirAll(home, 0777) c.Assert(err, jc.ErrorIsNil) utils.SetHome(home) err = os.MkdirAll(filepath.Join(home, ".local", "share"), 0777) c.Assert(err, jc.ErrorIsNil) s.oldJujuXDGDataHome = osenv.SetJujuXDGDataHome(filepath.Join(home, ".local", "share", "juju")) err = os.MkdirAll(osenv.JujuXDGDataHome(), 0777) c.Assert(err, jc.ErrorIsNil) err = os.MkdirAll(s.DataDir(), 0777) c.Assert(err, jc.ErrorIsNil) s.PatchEnvironment(osenv.JujuModelEnvKey, "admin") cfg, err := config.New(config.UseDefaults, (map[string]interface{})(s.sampleConfig())) c.Assert(err, jc.ErrorIsNil) s.ControllerStore = jujuclient.NewFileClientStore() ctx := testing.Context(c) environ, err := environs.Prepare( modelcmd.BootstrapContext(ctx), s.ControllerStore, environs.PrepareParams{ BaseConfig: cfg.AllAttrs(), Credential: cloud.NewEmptyCredential(), ControllerName: ControllerName, CloudName: "dummy", }, ) c.Assert(err, jc.ErrorIsNil) // sanity check we've got the correct environment. c.Assert(environ.Config().Name(), gc.Equals, "admin") s.PatchValue(&dummy.DataDir, s.DataDir()) s.LogDir = c.MkDir() s.PatchValue(&dummy.LogDir, s.LogDir) versions := PreferredDefaultVersions(environ.Config(), version.Binary{ Number: jujuversion.Current, Arch: "amd64", Series: "precise", }) current := version.Binary{ Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } versions = append(versions, current) // Upload tools for both preferred and fake default series s.DefaultToolsStorageDir = c.MkDir() s.PatchValue(&tools.DefaultBaseURL, s.DefaultToolsStorageDir) stor, err := filestorage.NewFileStorageWriter(s.DefaultToolsStorageDir) c.Assert(err, jc.ErrorIsNil) // Upload tools to both release and devel streams since config will dictate that we // end up looking in both places. envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", versions...) envtesting.AssertUploadFakeToolsVersions(c, stor, "devel", "devel", versions...) s.DefaultToolsStorage = stor s.PatchValue(&juju.JujuPublicKey, sstesting.SignedMetadataPublicKey) err = bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{}) c.Assert(err, jc.ErrorIsNil) s.BackingState = environ.(GetStater).GetStateInAPIServer() s.State, err = newState(environ, s.BackingState.MongoConnectionInfo()) c.Assert(err, jc.ErrorIsNil) apiInfo, err := environs.APIInfo(environ) c.Assert(err, jc.ErrorIsNil) apiInfo.Tag = s.AdminUserTag(c) apiInfo.Password = environ.Config().AdminSecret() s.APIState, err = api.Open(apiInfo, api.DialOpts{}) c.Assert(err, jc.ErrorIsNil) err = s.State.SetAPIHostPorts(s.APIState.APIHostPorts()) c.Assert(err, jc.ErrorIsNil) // Make sure the controller store has the controller api endpoint address set controller, err := s.ControllerStore.ControllerByName(ControllerName) c.Assert(err, jc.ErrorIsNil) controller.APIEndpoints = []string{s.APIState.APIHostPorts()[0][0].String()} err = s.ControllerStore.UpdateController(ControllerName, *controller) c.Assert(err, jc.ErrorIsNil) err = modelcmd.WriteCurrentController(ControllerName) c.Assert(err, jc.ErrorIsNil) s.Environ = environ // Insert expected values... servingInfo := state.StateServingInfo{ PrivateKey: testing.ServerKey, Cert: testing.ServerCert, CAPrivateKey: testing.CAKey, SharedSecret: "really, really secret", APIPort: 4321, StatePort: 1234, } s.State.SetStateServingInfo(servingInfo) }