// CloudSpec returns an environs.CloudSpec from a *state.State, // given the cloud, region and credential names. func CloudSpec( accessor state.CloudAccessor, cloudName, regionName string, credentialTag names.CloudCredentialTag, ) (environs.CloudSpec, error) { modelCloud, err := accessor.Cloud(cloudName) if err != nil { return environs.CloudSpec{}, errors.Trace(err) } var credential *cloud.Credential if credentialTag != (names.CloudCredentialTag{}) { credentialValue, err := accessor.CloudCredential(credentialTag) if err != nil { return environs.CloudSpec{}, errors.Trace(err) } credential = &credentialValue } return environs.MakeCloudSpec(modelCloud, cloudName, regionName, credential) }
// CreateModel creates a new model using the account and // model config specified in the args. func (m *ModelManagerAPI) CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) { result := params.ModelInfo{} canAddModel, err := m.authorizer.HasPermission(permission.AddModelAccess, m.state.ControllerTag()) if err != nil { return result, errors.Trace(err) } if !canAddModel { return result, common.ErrPerm } // Get the controller model first. We need it both for the state // server owner and the ability to get the config. controllerModel, err := m.state.ControllerModel() if err != nil { return result, errors.Trace(err) } ownerTag, err := names.ParseUserTag(args.OwnerTag) if err != nil { return result, errors.Trace(err) } var cloudTag names.CloudTag cloudRegionName := args.CloudRegion if args.CloudTag != "" { var err error cloudTag, err = names.ParseCloudTag(args.CloudTag) if err != nil { return result, errors.Trace(err) } } else { cloudTag = names.NewCloudTag(controllerModel.Cloud()) } if cloudRegionName == "" && cloudTag.Id() == controllerModel.Cloud() { cloudRegionName = controllerModel.CloudRegion() } cloud, err := m.state.Cloud(cloudTag.Id()) if err != nil { if errors.IsNotFound(err) && args.CloudTag != "" { // A cloud was specified, and it was not found. // Annotate the error with the supported clouds. clouds, err := m.state.Clouds() if err != nil { return result, errors.Trace(err) } cloudNames := make([]string, 0, len(clouds)) for tag := range clouds { cloudNames = append(cloudNames, tag.Id()) } sort.Strings(cloudNames) return result, errors.NewNotFound(err, fmt.Sprintf( "cloud %q not found, expected one of %q", cloudTag.Id(), cloudNames, )) } return result, errors.Annotate(err, "getting cloud definition") } var cloudCredentialTag names.CloudCredentialTag if args.CloudCredentialTag != "" { var err error cloudCredentialTag, err = names.ParseCloudCredentialTag(args.CloudCredentialTag) if err != nil { return result, errors.Trace(err) } } else { if ownerTag == controllerModel.Owner() { cloudCredentialTag, _ = controllerModel.CloudCredential() } else { // TODO(axw) check if the user has one and only one // cloud credential, and if so, use it? For now, we // require the user to specify a credential unless // the cloud does not require one. var hasEmpty bool for _, authType := range cloud.AuthTypes { if authType != jujucloud.EmptyAuthType { continue } hasEmpty = true break } if !hasEmpty { return result, errors.NewNotValid(nil, "no credential specified") } } } var credential *jujucloud.Credential if cloudCredentialTag != (names.CloudCredentialTag{}) { credentialValue, err := m.state.CloudCredential(cloudCredentialTag) if err != nil { return result, errors.Annotate(err, "getting credential") } credential = &credentialValue } cloudSpec, err := environs.MakeCloudSpec(cloud, cloudTag.Id(), cloudRegionName, credential) if err != nil { return result, errors.Trace(err) } controllerCfg, err := m.state.ControllerConfig() if err != nil { return result, errors.Trace(err) } newConfig, err := m.newModelConfig(cloudSpec, args, controllerModel) if err != nil { return result, errors.Annotate(err, "failed to create config") } // Create the Environ. env, err := environs.New(environs.OpenParams{ Cloud: cloudSpec, Config: newConfig, }) if err != nil { return result, errors.Annotate(err, "failed to open environ") } if err := env.Create(environs.CreateParams{ ControllerUUID: controllerCfg.ControllerUUID(), }); err != nil { return result, errors.Annotate(err, "failed to create environ") } storageProviderRegistry := stateenvirons.NewStorageProviderRegistry(env) // NOTE: check the agent-version of the config, and if it is > the current // version, it is not supported, also check existing tools, and if we don't // have tools for that version, also die. model, st, err := m.state.NewModel(state.ModelArgs{ CloudName: cloudTag.Id(), CloudRegion: cloudRegionName, CloudCredential: cloudCredentialTag, Config: newConfig, Owner: ownerTag, StorageProviderRegistry: storageProviderRegistry, }) if err != nil { return result, errors.Annotate(err, "failed to create new model") } defer st.Close() return m.getModelInfo(model.ModelTag()) }
// InitializeState should be called on the bootstrap machine's agent // configuration. It uses that information to create the controller, dial the // controller, and initialize it. It also generates a new password for the // bootstrap machine and calls Write to save the the configuration. // // The cfg values will be stored in the state's ModelConfig; the // machineCfg values will be used to configure the bootstrap Machine, // and its constraints will be also be used for the model-level // constraints. The connection to the controller will respect the // given timeout parameter. // // InitializeState returns the newly initialized state and bootstrap // machine. If it fails, the state may well be irredeemably compromised. func InitializeState( adminUser names.UserTag, c agent.ConfigSetter, args InitializeStateParams, dialOpts mongo.DialOpts, newPolicy state.NewPolicyFunc, ) (_ *state.State, _ *state.Machine, resultErr error) { if c.Tag() != names.NewMachineTag(agent.BootstrapMachineId) { return nil, nil, errors.Errorf("InitializeState not called with bootstrap machine's configuration") } servingInfo, ok := c.StateServingInfo() if !ok { return nil, nil, errors.Errorf("state serving information not available") } // N.B. no users are set up when we're initializing the state, // so don't use any tag or password when opening it. info, ok := c.MongoInfo() if !ok { return nil, nil, errors.Errorf("stateinfo not available") } info.Tag = nil info.Password = c.OldPassword() if err := initMongoAdminUser(info.Info, dialOpts, info.Password); err != nil { return nil, nil, errors.Annotate(err, "failed to initialize mongo admin user") } cloudCredentials := make(map[names.CloudCredentialTag]cloud.Credential) var cloudCredentialTag names.CloudCredentialTag if args.ControllerCloudCredential != nil && args.ControllerCloudCredentialName != "" { cloudCredentialTag = names.NewCloudCredentialTag(fmt.Sprintf( "%s/%s/%s", args.ControllerCloudName, adminUser.Canonical(), args.ControllerCloudCredentialName, )) cloudCredentials[cloudCredentialTag] = *args.ControllerCloudCredential } logger.Debugf("initializing address %v", info.Addrs) st, err := state.Initialize(state.InitializeParams{ Clock: clock.WallClock, ControllerModelArgs: state.ModelArgs{ Owner: adminUser, Config: args.ControllerModelConfig, Constraints: args.ModelConstraints, CloudName: args.ControllerCloudName, CloudRegion: args.ControllerCloudRegion, CloudCredential: cloudCredentialTag, StorageProviderRegistry: args.StorageProviderRegistry, }, CloudName: args.ControllerCloudName, Cloud: args.ControllerCloud, CloudCredentials: cloudCredentials, ControllerConfig: args.ControllerConfig, ControllerInheritedConfig: args.ControllerInheritedConfig, RegionInheritedConfig: args.RegionInheritedConfig, MongoInfo: info, MongoDialOpts: dialOpts, NewPolicy: newPolicy, }) if err != nil { return nil, nil, errors.Errorf("failed to initialize state: %v", err) } logger.Debugf("connected to initial state") defer func() { if resultErr != nil { st.Close() } }() servingInfo.SharedSecret = args.SharedSecret c.SetStateServingInfo(servingInfo) // Filter out any LXC or LXD bridge addresses from the machine addresses. args.BootstrapMachineAddresses = network.FilterBridgeAddresses(args.BootstrapMachineAddresses) if err = initAPIHostPorts(c, st, args.BootstrapMachineAddresses, servingInfo.APIPort); err != nil { return nil, nil, err } ssi := paramsStateServingInfoToStateStateServingInfo(servingInfo) if err := st.SetStateServingInfo(ssi); err != nil { return nil, nil, errors.Errorf("cannot set state serving info: %v", err) } m, err := initBootstrapMachine(c, st, args) if err != nil { return nil, nil, errors.Annotate(err, "cannot initialize bootstrap machine") } // Create the initial hosted model, with the model config passed to // bootstrap, which contains the UUID, name for the hosted model, // and any user supplied config. We also copy the authorized-keys // from the controller model. attrs := make(map[string]interface{}) for k, v := range args.HostedModelConfig { attrs[k] = v } attrs[config.AuthorizedKeysKey] = args.ControllerModelConfig.AuthorizedKeys() // Construct a CloudSpec to pass on to NewModelConfig below. cloudSpec, err := environs.MakeCloudSpec( args.ControllerCloud, args.ControllerCloudName, args.ControllerCloudRegion, args.ControllerCloudCredential, ) if err != nil { return nil, nil, errors.Trace(err) } controllerUUID := args.ControllerConfig.ControllerUUID() creator := modelmanager.ModelConfigCreator{Provider: args.Provider} hostedModelConfig, err := creator.NewModelConfig( cloudSpec, args.ControllerModelConfig, attrs, ) if err != nil { return nil, nil, errors.Annotate(err, "creating hosted model config") } provider, err := args.Provider(cloudSpec.Type) if err != nil { return nil, nil, errors.Annotate(err, "getting environ provider") } hostedModelEnv, err := provider.Open(environs.OpenParams{ Cloud: cloudSpec, Config: hostedModelConfig, }) if err != nil { return nil, nil, errors.Annotate(err, "opening hosted model environment") } if err := hostedModelEnv.Create(environs.CreateParams{ ControllerUUID: controllerUUID, }); err != nil { return nil, nil, errors.Annotate(err, "creating hosted model environment") } _, hostedModelState, err := st.NewModel(state.ModelArgs{ Owner: adminUser, Config: hostedModelConfig, Constraints: args.ModelConstraints, CloudName: args.ControllerCloudName, CloudRegion: args.ControllerCloudRegion, CloudCredential: cloudCredentialTag, StorageProviderRegistry: args.StorageProviderRegistry, }) if err != nil { return nil, nil, errors.Annotate(err, "creating hosted model") } hostedModelState.Close() return st, m, nil }
// Run initializes state for an environment. func (c *BootstrapCommand) Run(_ *cmd.Context) error { bootstrapParamsData, err := ioutil.ReadFile(c.BootstrapParamsFile) if err != nil { return errors.Annotate(err, "reading bootstrap params file") } var args instancecfg.StateInitializationParams if err := args.Unmarshal(bootstrapParamsData); err != nil { return errors.Trace(err) } err = c.ReadConfig("machine-0") if err != nil { return errors.Annotate(err, "cannot read config") } agentConfig := c.CurrentConfig() // agent.Jobs is an optional field in the agent config, and was // introduced after 1.17.2. We default to allowing units on // machine-0 if missing. jobs := agentConfig.Jobs() if len(jobs) == 0 { jobs = []multiwatcher.MachineJob{ multiwatcher.JobManageModel, multiwatcher.JobHostUnits, } } // Get the bootstrap machine's addresses from the provider. cloudSpec, err := environs.MakeCloudSpec( args.ControllerCloud, args.ControllerCloudName, args.ControllerCloudRegion, args.ControllerCloudCredential, ) if err != nil { return errors.Trace(err) } env, err := environs.New(environs.OpenParams{ Cloud: cloudSpec, Config: args.ControllerModelConfig, }) if err != nil { return errors.Annotate(err, "new environ") } newConfigAttrs := make(map[string]interface{}) // Check to see if a newer agent version has been requested // by the bootstrap client. desiredVersion, ok := args.ControllerModelConfig.AgentVersion() if ok && desiredVersion != jujuversion.Current { // If we have been asked for a newer version, ensure the newer // tools can actually be found, or else bootstrap won't complete. stream := envtools.PreferredStream(&desiredVersion, args.ControllerModelConfig.Development(), args.ControllerModelConfig.AgentStream()) logger.Infof("newer tools requested, looking for %v in stream %v", desiredVersion, stream) filter := tools.Filter{ Number: desiredVersion, Arch: arch.HostArch(), Series: series.HostSeries(), } _, toolsErr := envtools.FindTools(env, -1, -1, stream, filter) if toolsErr == nil { logger.Infof("tools are available, upgrade will occur after bootstrap") } if errors.IsNotFound(toolsErr) { // Newer tools not available, so revert to using the tools // matching the current agent version. logger.Warningf("newer tools for %q not available, sticking with version %q", desiredVersion, jujuversion.Current) newConfigAttrs["agent-version"] = jujuversion.Current.String() } else if toolsErr != nil { logger.Errorf("cannot find newer tools: %v", toolsErr) return toolsErr } } instances, err := env.Instances([]instance.Id{args.BootstrapMachineInstanceId}) if err != nil { return errors.Annotate(err, "getting bootstrap instance") } addrs, err := instances[0].Addresses() if err != nil { return errors.Annotate(err, "bootstrap instance addresses") } // When machine addresses are reported from state, they have // duplicates removed. We should do the same here so that // there is not unnecessary churn in the mongo replicaset. // TODO (cherylj) Add explicit unit tests for this - tracked // by bug #1544158. addrs = network.MergedAddresses([]network.Address{}, addrs) // Generate a private SSH key for the controllers, and add // the public key to the environment config. We'll add the // private key to StateServingInfo below. privateKey, publicKey, err := sshGenerateKey(config.JujuSystemKey) if err != nil { return errors.Annotate(err, "failed to generate system key") } authorizedKeys := config.ConcatAuthKeys(args.ControllerModelConfig.AuthorizedKeys(), publicKey) newConfigAttrs[config.AuthorizedKeysKey] = authorizedKeys // Generate a shared secret for the Mongo replica set, and write it out. sharedSecret, err := mongo.GenerateSharedSecret() if err != nil { return err } info, ok := agentConfig.StateServingInfo() if !ok { return fmt.Errorf("bootstrap machine config has no state serving info") } info.SharedSecret = sharedSecret info.SystemIdentity = privateKey err = c.ChangeConfig(func(agentConfig agent.ConfigSetter) error { agentConfig.SetStateServingInfo(info) return nil }) if err != nil { return fmt.Errorf("cannot write agent config: %v", err) } agentConfig = c.CurrentConfig() // Create system-identity file if err := agent.WriteSystemIdentityFile(agentConfig); err != nil { return err } if err := c.startMongo(addrs, agentConfig); err != nil { return errors.Annotate(err, "failed to start mongo") } controllerModelCfg, err := env.Config().Apply(newConfigAttrs) if err != nil { return errors.Annotate(err, "failed to update model config") } args.ControllerModelConfig = controllerModelCfg // Initialise state, and store any agent config (e.g. password) changes. var st *state.State var m *state.Machine err = c.ChangeConfig(func(agentConfig agent.ConfigSetter) error { var stateErr error dialOpts := mongo.DefaultDialOpts() // Set a longer socket timeout than usual, as the machine // will be starting up and disk I/O slower than usual. This // has been known to cause timeouts in queries. dialOpts.SocketTimeout = c.Timeout if dialOpts.SocketTimeout < minSocketTimeout { dialOpts.SocketTimeout = minSocketTimeout } // We shouldn't attempt to dial peers until we have some. dialOpts.Direct = true adminTag := names.NewLocalUserTag(adminUserName) st, m, stateErr = agentInitializeState( adminTag, agentConfig, agentbootstrap.InitializeStateParams{ StateInitializationParams: args, BootstrapMachineAddresses: addrs, BootstrapMachineJobs: jobs, SharedSecret: sharedSecret, Provider: environs.Provider, StorageProviderRegistry: stateenvirons.NewStorageProviderRegistry(env), }, dialOpts, stateenvirons.GetNewPolicyFunc( stateenvirons.GetNewEnvironFunc(environs.New), ), ) return stateErr }) if err != nil { return err } defer st.Close() // Populate the tools catalogue. if err := c.populateTools(st, env); err != nil { return err } // Populate the GUI archive catalogue. if err := c.populateGUIArchive(st, env); err != nil { // Do not stop the bootstrapping process for Juju GUI archive errors. logger.Warningf("cannot set up Juju GUI: %s", err) } else { logger.Debugf("Juju GUI successfully set up") } // Add custom image metadata to environment storage. if len(args.CustomImageMetadata) > 0 { if err := c.saveCustomImageMetadata(st, env, args.CustomImageMetadata); err != nil { return err } } // bootstrap machine always gets the vote return m.SetHasVote(true) }