func newStorageProvisionerAPI(st *state.State, resources facade.Resources, authorizer facade.Authorizer) (*StorageProvisionerAPI, error) { env, err := stateenvirons.GetNewEnvironFunc(environs.New)(st) if err != nil { return nil, errors.Annotate(err, "getting environ") } registry := stateenvirons.NewStorageProviderRegistry(env) pm := poolmanager.New(state.NewStateSettings(st), registry) return NewStorageProvisionerAPI(stateShim{st}, resources, authorizer, registry, pm) }
func (s *provisionerSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) s.factory = factory.NewFactory(s.State) s.resources = common.NewResources() // Create the resource registry separately to track invocations to // Register. s.resources = common.NewResources() s.AddCleanup(func(_ *gc.C) { s.resources.StopAll() }) env, err := stateenvirons.GetNewEnvironFunc(environs.New)(s.State) c.Assert(err, jc.ErrorIsNil) registry := stateenvirons.NewStorageProviderRegistry(env) pm := poolmanager.New(state.NewStateSettings(s.State), registry) s.authorizer = &apiservertesting.FakeAuthorizer{ Tag: names.NewMachineTag("0"), EnvironManager: true, } backend := storageprovisioner.NewStateBackend(s.State) s.api, err = storageprovisioner.NewStorageProvisionerAPI(backend, s.resources, s.authorizer, registry, pm) c.Assert(err, jc.ErrorIsNil) }
// 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()) }
// NewProvisionerAPI creates a new server-side ProvisionerAPI facade. func NewProvisionerAPI(st *state.State, resources facade.Resources, authorizer facade.Authorizer) (*ProvisionerAPI, error) { if !authorizer.AuthMachineAgent() && !authorizer.AuthModelManager() { return nil, common.ErrPerm } getAuthFunc := func() (common.AuthFunc, error) { isModelManager := authorizer.AuthModelManager() isMachineAgent := authorizer.AuthMachineAgent() authEntityTag := authorizer.GetAuthTag() return func(tag names.Tag) bool { if isMachineAgent && tag == authEntityTag { // A machine agent can always access its own machine. return true } switch tag := tag.(type) { case names.MachineTag: parentId := state.ParentId(tag.Id()) if parentId == "" { // All top-level machines are accessible by the // environment manager. return isModelManager } // All containers with the authenticated machine as a // parent are accessible by it. // TODO(dfc) sometimes authEntity tag is nil, which is fine because nil is // only equal to nil, but it suggests someone is passing an authorizer // with a nil tag. return isMachineAgent && names.NewMachineTag(parentId) == authEntityTag default: return false } }, nil } getAuthOwner := func() (common.AuthFunc, error) { return authorizer.AuthOwner, nil } model, err := st.Model() if err != nil { return nil, err } configGetter := stateenvirons.EnvironConfigGetter{st} env, err := environs.GetEnviron(configGetter, environs.New) if err != nil { return nil, err } urlGetter := common.NewToolsURLGetter(model.UUID(), st) storageProviderRegistry := stateenvirons.NewStorageProviderRegistry(env) return &ProvisionerAPI{ Remover: common.NewRemover(st, false, getAuthFunc), StatusSetter: common.NewStatusSetter(st, getAuthFunc), StatusGetter: common.NewStatusGetter(st, getAuthFunc), DeadEnsurer: common.NewDeadEnsurer(st, getAuthFunc), PasswordChanger: common.NewPasswordChanger(st, getAuthFunc), LifeGetter: common.NewLifeGetter(st, getAuthFunc), StateAddresser: common.NewStateAddresser(st), APIAddresser: common.NewAPIAddresser(st, resources), ModelWatcher: common.NewModelWatcher(st, resources, authorizer), ModelMachinesWatcher: common.NewModelMachinesWatcher(st, resources, authorizer), ControllerConfigAPI: common.NewControllerConfig(st), InstanceIdGetter: common.NewInstanceIdGetter(st, getAuthFunc), ToolsFinder: common.NewToolsFinder(configGetter, st, urlGetter), ToolsGetter: common.NewToolsGetter(st, configGetter, st, urlGetter, getAuthOwner), st: st, resources: resources, authorizer: authorizer, configGetter: configGetter, storageProviderRegistry: storageProviderRegistry, storagePoolManager: poolmanager.New(state.NewStateSettings(st), storageProviderRegistry), getAuthFunc: getAuthFunc, }, 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) }