// validateUploadAllowed returns an error if an attempt to upload tools should // not be allowed. func validateUploadAllowed(env environs.Environ, toolsArch, toolsSeries *string, validator constraints.Validator) error { // Now check that the architecture and series for which we are setting up an // environment matches that from which we are bootstrapping. hostArch := arch.HostArch() // We can't build tools for a different architecture if one is specified. if toolsArch != nil && *toolsArch != hostArch { return fmt.Errorf("cannot use agent built for %q using a machine running on %q", *toolsArch, hostArch) } hostOS := jujuos.HostOS() if toolsSeries != nil { toolsSeriesOS, err := series.GetOSFromSeries(*toolsSeries) if err != nil { return errors.Trace(err) } if !toolsSeriesOS.EquivalentTo(hostOS) { return errors.Errorf("cannot use agent built for %q using a machine running %q", *toolsSeries, hostOS) } } // If no architecture is specified, ensure the target provider supports instances matching our architecture. if _, err := validator.Validate(constraints.Value{Arch: &hostArch}); err != nil { return errors.Errorf( "model %q of type %s does not support instances running on %q", env.Config().Name(), env.Config().Type(), hostArch, ) } return nil }
// Bootstrap bootstraps the given environment. The supplied constraints are // used to provision the instance, and are also set within the bootstrapped // environment. func Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args environs.BootstrapParams) error { cfg := environ.Config() network.InitializeFromConfig(cfg) if secret := cfg.AdminSecret(); secret == "" { return fmt.Errorf("environment configuration has no admin-secret") } if authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()); len(authKeys) == 0 { // Apparently this can never happen, so it's not tested. But, one day, // Config will act differently (it's pretty crazy that, AFAICT, the // authorized-keys are optional config settings... but it's impossible // to actually *create* a config without them)... and when it does, // we'll be here to catch this problem early. return fmt.Errorf("environment configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return fmt.Errorf("environment configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return fmt.Errorf("environment configuration has no ca-private-key") } // Write out the bootstrap-init file, and confirm storage is writeable. if err := environs.VerifyStorage(environ.Storage()); err != nil { return err } logger.Debugf("environment %q supports service/machine networks: %v", environ.Name(), environ.SupportNetworks()) logger.Infof("bootstrapping environment %q", environ.Name()) return environ.Bootstrap(ctx, args) }
func assertEnvironDestroyed(c *gc.C, env environs.Environ, store configstore.Storage) { _, err := store.ReadInfo(env.Config().Name()) c.Assert(err, jc.Satisfies, errors.IsNotFound) _, err = env.Instances([]instance.Id{"invalid"}) c.Assert(err, gc.ErrorMatches, "environment has been destroyed") }
func (s *localJujuTestSuite) makeFakeUpstartScripts(c *gc.C, env environs.Environ, ) (mongoService *upstart.Service, machineAgent *upstart.Service) { upstartDir := c.MkDir() s.PatchValue(&upstart.InitDir, upstartDir) s.MakeTool(c, "start", `echo "some-service start/running, process 123"`) namespace := env.Config().AllAttrs()["namespace"].(string) mongoConf := common.Conf{ Desc: "fake mongo", Cmd: "echo FAKE", } mongoService = upstart.NewService(mongo.ServiceName(namespace), mongoConf) err := mongoService.Install() c.Assert(err, gc.IsNil) c.Assert(mongoService.Installed(), jc.IsTrue) agentConf := common.Conf{ Desc: "fake agent", Cmd: "echo FAKE", } machineAgent = upstart.NewService(fmt.Sprintf("juju-agent-%s", namespace), agentConf) err = machineAgent.Install() c.Assert(err, gc.IsNil) c.Assert(machineAgent.Installed(), jc.IsTrue) return mongoService, machineAgent }
func destroyStorage(env environs.Environ) error { logger.Infof("destroying storage") environConfig := env.Config() storageProviderTypes, ok := registry.EnvironStorageProviders(environConfig.Type()) if !ok { return nil } for _, storageProviderType := range storageProviderTypes { storageProvider, err := registry.StorageProvider(storageProviderType) if err != nil { return errors.Trace(err) } if !storageProvider.Dynamic() { continue } if storageProvider.Scope() != storage.ScopeEnviron { continue } if err := destroyVolumes(environConfig, storageProviderType, storageProvider); err != nil { return errors.Trace(err) } // TODO(axw) destroy env-level filesystems when we have them. } return nil }
// newState returns a new State that uses the given environment. // The environment must have already been bootstrapped. func newState(environ environs.Environ, mongoInfo *mongo.MongoInfo) (*state.State, error) { config := environ.Config() password := config.AdminSecret() if password == "" { return nil, fmt.Errorf("cannot connect without admin-secret") } modelTag := names.NewModelTag(config.UUID()) mongoInfo.Password = password opts := mongo.DefaultDialOpts() st, err := state.Open(modelTag, mongoInfo, opts, environs.NewStatePolicy()) if errors.IsUnauthorized(errors.Cause(err)) { // We try for a while because we might succeed in // connecting to mongo before the state has been // initialized and the initial password set. for a := redialStrategy.Start(); a.Next(); { st, err = state.Open(modelTag, mongoInfo, opts, environs.NewStatePolicy()) if !errors.IsUnauthorized(errors.Cause(err)) { break } } if err != nil { return nil, err } } else if err != nil { return nil, err } if err := updateSecrets(environ, st); err != nil { st.Close() return nil, fmt.Errorf("unable to push secrets: %v", err) } return st, nil }
func SetImageMetadata(env environs.Environ, series, arches []string, out *[]*imagemetadata.ImageMetadata) error { hasRegion, ok := env.(simplestreams.HasRegion) if !ok { return nil } sources, err := environs.ImageMetadataSources(env) if err != nil { return errors.Trace(err) } region, err := hasRegion.Region() if err != nil { return errors.Trace(err) } imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ CloudSpec: region, Series: series, Arches: arches, Stream: env.Config().ImageStream(), }) imageMetadata, _, err := imagemetadata.Fetch(sources, imageConstraint) if err != nil { return errors.Trace(err) } *out = imageMetadata return nil }
// setBootstrapEndpointAddress writes the API endpoint address of the // bootstrap server into the connection information. This should only be run // once directly after Bootstrap. It assumes that there is just one instance // in the environment - the bootstrap instance. func (c *bootstrapCommand) setBootstrapEndpointAddress(environ environs.Environ) error { instances, err := allInstances(environ) if err != nil { return errors.Trace(err) } length := len(instances) if length == 0 { return errors.Errorf("found no instances, expected at least one") } if length > 1 { logger.Warningf("expected one instance, got %d", length) } bootstrapInstance := instances[0] // Don't use c.ConnectionEndpoint as it attempts to contact the state // server if no addresses are found in connection info. netAddrs, err := bootstrapInstance.Addresses() if err != nil { return errors.Annotate(err, "failed to get bootstrap instance addresses") } cfg := environ.Config() apiPort := cfg.APIPort() apiHostPorts := network.AddressesWithPort(netAddrs, apiPort) return juju.UpdateControllerAddresses(c.ClientStore(), c.controllerName, nil, apiHostPorts...) }
func (s *localJujuTestSuite) makeFakeInitScripts(c *gc.C, env environs.Environ) (installable, installable) { s.MakeTool(c, "start", `echo "some-service start/running, process 123"`) namespace := env.Config().AllAttrs()["namespace"].(string) // Mongo first... mongoName := mongo.ServiceName(namespace) mongoConf := common.Conf{ Desc: "fake mongo", ExecStart: "echo FAKE", } mongoService := local.NewService(mongoName, mongoConf, s.svcData) s.svcData.SetStatus(mongoName, "installed") installed, err := mongoService.Installed() c.Assert(err, jc.ErrorIsNil) c.Check(installed, jc.IsTrue) // ...then the machine agent agentName := fmt.Sprintf("juju-agent-%s", namespace) agentConf := common.Conf{ Desc: "fake agent", ExecStart: "echo FAKE", } agentService := local.NewService(agentName, agentConf, s.svcData) s.svcData.SetStatus(agentName, "installed") installed, err = agentService.Installed() c.Assert(err, jc.ErrorIsNil) c.Check(installed, jc.IsTrue) return mongoService, agentService }
// StateInfo is a reusable implementation of Environ.StateInfo, available to // providers that also use the other functionality from this file. func StateInfo(env environs.Environ) (*state.Info, *api.Info, error) { st, err := bootstrap.LoadState(env.Storage()) if err != nil { return nil, nil, err } config := env.Config() if _, hasCert := config.CACert(); !hasCert { return nil, nil, fmt.Errorf("no CA certificate in environment configuration") } // Wait for the addresses of at least one of the instances to become available. logger.Debugf("waiting for addresses of state server instances %v", st.StateInstances) var addresses []string for a := LongAttempt.Start(); len(addresses) == 0 && a.Next(); { insts, err := env.Instances(st.StateInstances) if err != nil && err != environs.ErrPartialInstances { logger.Debugf("error getting state instances: %v", err.Error()) return nil, nil, err } addresses = getAddresses(insts) } if len(addresses) == 0 { return nil, nil, fmt.Errorf("timed out waiting for addresses from %v", st.StateInstances) } stateInfo, apiInfo := getStateInfo(config, addresses) return stateInfo, apiInfo, nil }
// validateUploadAllowed returns an error if an attempt to upload tools should // not be allowed. func validateUploadAllowed(env environs.Environ, toolsArch *string) error { // Now check that the architecture for which we are setting up an // environment matches that from which we are bootstrapping. hostArch := arch.HostArch() // We can't build tools for a different architecture if one is specified. if toolsArch != nil && *toolsArch != hostArch { return fmt.Errorf("cannot build tools for %q using a machine running on %q", *toolsArch, hostArch) } // If no architecture is specified, ensure the target provider supports instances matching our architecture. supportedArchitectures, err := env.SupportedArchitectures() if err != nil { return fmt.Errorf( "no packaged tools available and cannot determine environment's supported architectures: %v", err) } archSupported := false for _, arch := range supportedArchitectures { if hostArch == arch { archSupported = true break } } if !archSupported { envType := env.Config().Type() return errors.Errorf("environment %q of type %s does not support instances running on %q", env.Config().Name(), envType, hostArch) } return nil }
// GetMetadataSources returns the sources to use when looking for // simplestreams tools metadata for the given stream. func GetMetadataSources(env environs.Environ) ([]simplestreams.DataSource, error) { config := env.Config() // Add configured and environment-specific datasources. var sources []simplestreams.DataSource if userURL, ok := config.AgentMetadataURL(); ok { verify := utils.VerifySSLHostnames if !config.SSLHostnameVerification() { verify = utils.NoVerifySSLHostnames } sources = append(sources, simplestreams.NewURLSignedDataSource(conf.AgentMetadataURLKey, userURL, juju.JujuPublicKey, verify, simplestreams.SPECIFIC_CLOUD_DATA, false)) } envDataSources, err := environmentDataSources(env) if err != nil { return nil, err } sources = append(sources, envDataSources...) // Add the default, public datasource. defaultURL, err := ToolsURL(DefaultBaseURL) if err != nil { return nil, err } if defaultURL != "" { sources = append(sources, simplestreams.NewURLSignedDataSource("default simplestreams", defaultURL, juju.JujuPublicKey, utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, true)) } return sources, nil }
// StartInstanceWithParams is a test helper function that starts an instance // with the given parameters, and a plausible but invalid configuration, and // returns the result of Environ.StartInstance. The provided params's // MachineConfig and Tools field values will be ignored. func StartInstanceWithParams( env environs.Environ, machineId string, params environs.StartInstanceParams, networks []string, ) ( instance.Instance, *instance.HardwareCharacteristics, []network.Info, error, ) { series := config.PreferredSeries(env.Config()) agentVersion, ok := env.Config().AgentVersion() if !ok { return nil, nil, nil, fmt.Errorf("missing agent version in environment config") } possibleTools, err := tools.FindInstanceTools( env, agentVersion, series, params.Constraints.Arch, ) if err != nil { return nil, nil, nil, err } machineNonce := "fake_nonce" stateInfo := FakeStateInfo(machineId) apiInfo := FakeAPIInfo(machineId) machineConfig := environs.NewMachineConfig( machineId, machineNonce, networks, stateInfo, apiInfo) params.Tools = possibleTools params.MachineConfig = machineConfig return env.StartInstance(params) }
// StartInstanceWithParams is a test helper function that starts an instance // with the given parameters, and a plausible but invalid configuration, and // returns the result of Environ.StartInstance. The provided params's // InstanceConfig and Tools field values will be ignored. func StartInstanceWithParams( env environs.Environ, machineId string, params environs.StartInstanceParams, networks []string, ) ( *environs.StartInstanceResult, error, ) { preferredSeries := config.PreferredSeries(env.Config()) agentVersion, ok := env.Config().AgentVersion() if !ok { return nil, errors.New("missing agent version in model config") } filter := coretools.Filter{ Number: agentVersion, Series: preferredSeries, } if params.Constraints.Arch != nil { filter.Arch = *params.Constraints.Arch } stream := tools.PreferredStream(&agentVersion, env.Config().Development(), env.Config().AgentStream()) possibleTools, err := tools.FindTools(env, -1, -1, stream, filter) if err != nil { return nil, errors.Trace(err) } if params.ImageMetadata == nil { if err := SetImageMetadata( env, possibleTools.AllSeries(), possibleTools.Arches(), ¶ms.ImageMetadata, ); err != nil { return nil, errors.Trace(err) } } machineNonce := "fake_nonce" stateInfo := FakeStateInfo(machineId) apiInfo := FakeAPIInfo(machineId) instanceConfig, err := instancecfg.NewInstanceConfig( machineId, machineNonce, imagemetadata.ReleasedStream, preferredSeries, "", true, networks, stateInfo, apiInfo, ) if err != nil { return nil, errors.Trace(err) } eUUID, _ := env.Config().UUID() instanceConfig.Tags[tags.JujuModel] = eUUID params.Tools = possibleTools params.InstanceConfig = instanceConfig return env.StartInstance(params) }
func assertEnvironNotDestroyed(c *gc.C, env environs.Environ, store configstore.Storage) { info, err := store.ReadInfo(env.Config().Name()) c.Assert(err, gc.IsNil) c.Assert(info.Initialized(), jc.IsTrue) _, err = environs.NewFromName(env.Config().Name(), store) c.Assert(err, gc.IsNil) }
// Destroy is a common implementation of the Destroy method defined on // environs.Environ; we strongly recommend that this implementation be // used when writing a new provider. func Destroy(env environs.Environ) error { logger.Infof("destroying model %q", env.Config().Name()) if err := destroyInstances(env); err != nil { return errors.Annotate(err, "destroying instances") } if err := destroyStorage(env); err != nil { return errors.Annotate(err, "destroying storage") } return nil }
// Bootstrap is a common implementation of the Bootstrap method defined on // environs.Environ; we strongly recommend that this implementation be used // when writing a new provider. func Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams) (err error) { // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to make sure that only one succeeds. var inst instance.Instance defer func() { handleBootstrapError(err, ctx, inst, env) }() network.InitializeFromConfig(env.Config()) // First thing, ensure we have tools otherwise there's no point. selectedTools, err := EnsureBootstrapTools(ctx, env, config.PreferredSeries(env.Config()), args.Constraints.Arch) if err != nil { return err } // Get the bootstrap SSH client. Do this early, so we know // not to bother with any of the below if we can't finish the job. client := ssh.DefaultClient if client == nil { // This should never happen: if we don't have OpenSSH, then // go.crypto/ssh should be used with an auto-generated key. return fmt.Errorf("no SSH client available") } privateKey, err := GenerateSystemSSHKey(env) if err != nil { return err } machineConfig := environs.NewBootstrapMachineConfig(privateKey) fmt.Fprintln(ctx.GetStderr(), "Launching instance") inst, hw, _, err := env.StartInstance(environs.StartInstanceParams{ Constraints: args.Constraints, Tools: selectedTools, MachineConfig: machineConfig, Placement: args.Placement, }) if err != nil { return fmt.Errorf("cannot start bootstrap instance: %v", err) } fmt.Fprintf(ctx.GetStderr(), " - %s\n", inst.Id()) machineConfig.InstanceId = inst.Id() machineConfig.HardwareCharacteristics = hw err = bootstrap.SaveState( env.Storage(), &bootstrap.BootstrapState{ StateInstances: []instance.Id{inst.Id()}, }) if err != nil { return fmt.Errorf("cannot save state: %v", err) } return FinishBootstrap(ctx, client, inst, machineConfig) }
func findInstanceTools(env environs.Environ, series, arch string) (*tools.Tools, error) { agentVersion, ok := env.Config().AgentVersion() if !ok { return nil, fmt.Errorf("no agent version set in environment configuration") } possibleTools, err := envtools.FindInstanceTools(env, agentVersion, series, &arch) if err != nil { return nil, err } return possibleTools[0], nil }
func publicAttrs(e environs.Environ) map[string]interface{} { cfg := e.Config() secrets, err := e.Provider().SecretAttrs(cfg) if err != nil { panic(err) } attrs := cfg.AllAttrs() for attr := range secrets { delete(attrs, attr) } return attrs }
// findAvailableTools returns a list of available tools, // including tools that may be locally built and then // uploaded. Tools that need to be built will have an // empty URL. func findAvailableTools(env environs.Environ, arch *string, upload bool) (coretools.List, error) { if upload { // We're forcing an upload: ensure we can do so. if err := validateUploadAllowed(env, arch); err != nil { return nil, err } return locallyBuildableTools(), nil } // We're not forcing an upload, so look for tools // in the environment's simplestreams search paths // for existing tools. var vers *version.Number if agentVersion, ok := env.Config().AgentVersion(); ok { vers = &agentVersion } dev := version.Current.IsDev() || env.Config().Development() logger.Debugf("looking for bootstrap tools: version=%v", vers) toolsList, findToolsErr := findBootstrapTools(env, vers, arch, dev) if findToolsErr != nil && !errors.IsNotFound(findToolsErr) { return nil, findToolsErr } if !dev || vers != nil { // We are not running a development build, or agent-version // was specified; the only tools available are the ones we've // just found. return toolsList, findToolsErr } // The tools located may not include the ones that the // provider requires. We are running a development build, // so augment the list of tools with those that we can build // locally. // Collate the set of arch+series that are externally available // so we can see if we need to build any locally. If we need // to, only then do we validate that we can upload (which // involves a potentially expensive SupportedArchitectures call). var archSeries set.Strings for _, tools := range toolsList { archSeries.Add(tools.Version.Arch + tools.Version.Series) } var localToolsList coretools.List for _, tools := range locallyBuildableTools() { if !archSeries.Contains(tools.Version.Arch + tools.Version.Series) { localToolsList = append(localToolsList, tools) } } if len(localToolsList) == 0 || validateUploadAllowed(env, arch) != nil { return toolsList, findToolsErr } return append(toolsList, localToolsList...), nil }
// NewConn returns a new Conn that uses the // given environment. The environment must have already // been bootstrapped. func NewConn(environ environs.Environ) (*Conn, error) { info, _, err := environ.StateInfo() if err != nil { return nil, err } password := environ.Config().AdminSecret() if password == "" { return nil, fmt.Errorf("cannot connect without admin-secret") } err = environs.CheckEnvironment(environ) if err != nil { return nil, err } info.Password = password opts := mongo.DefaultDialOpts() st, err := state.Open(info, opts, environs.NewStatePolicy()) if errors.IsUnauthorized(err) { logger.Infof("authorization error while connecting to state server; retrying") // We can't connect with the administrator password,; // perhaps this was the first connection and the // password has not been changed yet. info.Password = utils.UserPasswordHash(password, utils.CompatSalt) // We try for a while because we might succeed in // connecting to mongo before the state has been // initialized and the initial password set. for a := redialStrategy.Start(); a.Next(); { st, err = state.Open(info, opts, environs.NewStatePolicy()) if !errors.IsUnauthorized(err) { break } } if err != nil { return nil, err } if err := st.SetAdminMongoPassword(password); err != nil { return nil, err } } else if err != nil { return nil, err } conn := &Conn{ Environ: environ, State: st, } if err := conn.updateSecrets(); err != nil { conn.Close() return nil, fmt.Errorf("unable to push secrets: %v", err) } return conn, nil }
func environAPIInfo(environ environs.Environ) (*api.Info, error) { _, info, err := environ.StateInfo() if err != nil { return nil, err } info.Tag = "user-admin" password := environ.Config().AdminSecret() if password == "" { return nil, fmt.Errorf("cannot connect without admin-secret") } info.Password = password return info, nil }
// findBootstrapTools returns a tools.List containing only those tools with // which it would be reasonable to launch an environment's first machine, // given the supplied constraints. If a specific agent version is not requested, // all tools matching the current major.minor version are chosen. func findBootstrapTools(env environs.Environ, vers *version.Number, arch *string) (list coretools.List, err error) { // Construct a tools filter. cliVersion := version.Current.Number var filter coretools.Filter if arch != nil { filter.Arch = *arch } if vers != nil { filter.Number = *vers } stream := envtools.PreferredStream(vers, env.Config().Development(), env.Config().AgentStream()) return findTools(env, cliVersion.Major, cliVersion.Minor, stream, filter) }
func environAPIInfo(environ environs.Environ, user names.UserTag) (*api.Info, error) { config := environ.Config() password := config.AdminSecret() if password == "" { return nil, fmt.Errorf("cannot connect to API servers without admin-secret") } info, err := environs.APIInfo(environ) if err != nil { return nil, err } info.Tag = user info.Password = password return info, nil }
func environAPIInfo(environ environs.Environ, user names.Tag) (*api.Info, error) { config := environ.Config() password := config.AdminSecret() info, err := environs.APIInfo(environ) if err != nil { return nil, err } info.Tag = user info.Password = password if info.Tag == nil { info.UseMacaroons = true } return info, nil }
// Bootstrap is a common implementation of the Bootstrap method defined on // environs.Environ; we strongly recommend that this implementation be used // when writing a new provider. func Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, err error) { // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to make sure that only one succeeds. var inst instance.Instance defer func() { handleBootstrapError(err, ctx, inst, env) }() // First thing, ensure we have tools otherwise there's no point. series = config.PreferredSeries(env.Config()) availableTools, err := args.AvailableTools.Match(coretools.Filter{Series: series}) if err != nil { return "", "", nil, err } // Get the bootstrap SSH client. Do this early, so we know // not to bother with any of the below if we can't finish the job. client := ssh.DefaultClient if client == nil { // This should never happen: if we don't have OpenSSH, then // go.crypto/ssh should be used with an auto-generated key. return "", "", nil, fmt.Errorf("no SSH client available") } machineConfig, err := environs.NewBootstrapMachineConfig(args.Constraints, series) if err != nil { return "", "", nil, err } machineConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() machineConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() fmt.Fprintln(ctx.GetStderr(), "Launching instance") inst, hw, _, err := env.StartInstance(environs.StartInstanceParams{ Constraints: args.Constraints, Tools: availableTools, MachineConfig: machineConfig, Placement: args.Placement, }) if err != nil { return "", "", nil, fmt.Errorf("cannot start bootstrap instance: %v", err) } fmt.Fprintf(ctx.GetStderr(), " - %s\n", inst.Id()) err = SaveState(env.Storage(), &BootstrapState{ StateInstances: []instance.Id{inst.Id()}, }) if err != nil { return "", "", nil, fmt.Errorf("cannot save state: %v", err) } finalize := func(ctx environs.BootstrapContext, mcfg *cloudinit.MachineConfig) error { mcfg.InstanceId = inst.Id() mcfg.HardwareCharacteristics = hw if err := environs.FinishMachineConfig(mcfg, env.Config()); err != nil { return err } return FinishBootstrap(ctx, client, inst, mcfg) } return *hw.Arch, series, finalize, nil }
// setBootstrapToolsVersion updates the agent-version configuration attribute. func setBootstrapToolsVersion(environ environs.Environ, toolsVersion version.Number) error { cfg := environ.Config() if agentVersion, _ := cfg.AgentVersion(); agentVersion != toolsVersion { cfg, err := cfg.Apply(map[string]interface{}{ "agent-version": toolsVersion.String(), }) if err == nil { err = environ.SetConfig(cfg) } if err != nil { return errors.Errorf("failed to update model configuration: %v", err) } } return nil }
func assertInterfaces(c *gc.C, e environs.Environ, opc chan dummy.Operation, expectInstId instance.Id, expectInfo []network.InterfaceInfo) { select { case op := <-opc: netOp, ok := op.(dummy.OpNetworkInterfaces) if !ok { c.Fatalf("unexpected op: %#v", op) } c.Check(netOp.Env, gc.Equals, e.Config().Name()) c.Check(netOp.InstanceId, gc.Equals, expectInstId) c.Check(netOp.Info, jc.DeepEquals, expectInfo) return case <-time.After(testing.ShortWait): c.Fatalf("time out wating for operation") } }
// SetBootstrapEndpointAddress writes the API endpoint address of the // bootstrap server into the connection information. This should only be run // once directly after Bootstrap. It assumes that there is just one instance // in the environment - the bootstrap instance. func (c *BootstrapCommand) SetBootstrapEndpointAddress(environ environs.Environ) error { instances, err := allInstances(environ) if err != nil { return errors.Trace(err) } length := len(instances) if length == 0 { return errors.Errorf("found no instances, expected at least one") } if length > 1 { logger.Warningf("expected one instance, got %d", length) } bootstrapInstance := instances[0] cfg := environ.Config() info, err := envcmd.ConnectionInfoForName(c.ConnectionName()) if err != nil { return errors.Annotate(err, "failed to get connection info") } // Don't use c.ConnectionEndpoint as it attempts to contact the state // server if no addresses are found in connection info. endpoint := info.APIEndpoint() netAddrs, err := bootstrapInstance.Addresses() if err != nil { return errors.Annotate(err, "failed to get bootstrap instance addresses") } apiPort := cfg.APIPort() apiHostPorts := network.AddressesWithPort(netAddrs, apiPort) addrs, hosts, addrsChanged := prepareEndpointsForCaching( info, [][]network.HostPort{apiHostPorts}, network.HostPort{}, ) if !addrsChanged { // Something's wrong we already have cached addresses? return errors.Annotate(err, "cached API endpoints unexpectedly exist") } endpoint.Addresses = addrs endpoint.Hostnames = hosts writer, err := c.ConnectionWriter() if err != nil { return errors.Annotate(err, "failed to get connection writer") } writer.SetAPIEndpoint(endpoint) err = writer.Write() if err != nil { return errors.Annotate(err, "failed to write API endpoint to connection info") } return nil }
// EnsureToolsAvailability verifies the tools are available. If no tools are // found, it will automatically synchronize them. func EnsureToolsAvailability(ctx environs.BootstrapContext, env environs.Environ, series string, toolsArch *string) (coretools.List, error) { cfg := env.Config() var vers *version.Number if agentVersion, ok := cfg.AgentVersion(); ok { vers = &agentVersion } logger.Debugf( "looking for bootstrap tools: series=%q, arch=%v, version=%v", series, toolsArch, vers, ) params := envtools.BootstrapToolsParams{ Version: vers, Arch: toolsArch, Series: series, // If vers.Build>0, the tools may have been uploaded in this session. // Allow retries, so we wait until the storage has caught up. AllowRetry: vers != nil && vers.Build > 0, } toolsList, err := envtools.FindBootstrapTools(env, params) if err == nil { return toolsList, nil } else if !errors.IsNotFound(err) { return nil, err } // Only automatically upload tools for dev versions. if !version.Current.IsDev() { return nil, fmt.Errorf("cannot upload bootstrap tools: %v", noToolsNoUploadMessage) } // No tools available so our only hope is to build locally and upload. logger.Warningf("no prepackaged tools available") uploadSeries := SeriesToUpload(cfg, nil) if series != "" { uploadSeries = append(uploadSeries, series) } if err := UploadTools(ctx, env, toolsArch, false, uploadSeries...); err != nil { logger.Errorf("%s", noToolsMessage) return nil, fmt.Errorf("cannot upload bootstrap tools: %v", err) } // TODO(axw) have uploadTools return the list of tools in the target, and use that. params.AllowRetry = true if toolsList, err = envtools.FindBootstrapTools(env, params); err != nil { return nil, fmt.Errorf("cannot find bootstrap tools: %v", err) } return toolsList, nil }