// 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 }
// handleBootstrapError cleans up after a failed bootstrap. func handleBootstrapError(err error, ctx environs.BootstrapContext, inst instance.Instance, env environs.Environ) { if err == nil { return } logger.Errorf("bootstrap failed: %v", err) ch := make(chan os.Signal, 1) ctx.InterruptNotify(ch) defer ctx.StopInterruptNotify(ch) defer close(ch) go func() { for _ = range ch { fmt.Fprintln(ctx.GetStderr(), "Cleaning up failed bootstrap") } }() if inst != nil { fmt.Fprintln(ctx.GetStderr(), "Stopping instance...") if stoperr := env.StopInstances(inst.Id()); stoperr != nil { logger.Errorf("cannot stop failed bootstrap instance %q: %v", inst.Id(), stoperr) } else { // set to nil so we know we can safely delete the state file inst = nil } } // We only delete the bootstrap state file if either we didn't // start an instance, or we managed to cleanly stop it. if inst == nil { if rmerr := bootstrap.DeleteStateFile(env.Storage()); rmerr != nil { logger.Errorf("cannot delete bootstrap state file: %v", rmerr) } } }
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 }
// 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") }
// 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 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 }
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 }
// 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 }
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 }
// 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 }
// 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 }
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) }
func makeTestMetadata(c *gc.C, env environs.Environ, series, location string, im []*imagemetadata.ImageMetadata) { cloudSpec := simplestreams.CloudSpec{ Region: location, Endpoint: "https://management.core.windows.net/", } err := imagemetadata.MergeAndWriteMetadata(series, im, &cloudSpec, env.Storage()) c.Assert(err, gc.IsNil) }
// populateTools stores uploaded tools in provider storage // and updates the tools metadata. // // TODO(axw) store tools in gridfs, catalogue in state. func (c *BootstrapCommand) populateTools(env environs.Environ) error { agentConfig := c.CurrentConfig() dataDir := agentConfig.DataDir() tools, err := agenttools.ReadTools(dataDir, version.Current) if err != nil { return err } if !strings.HasPrefix(tools.URL, "file://") { // Nothing to do since the tools were not uploaded. return nil } // This is a hack: providers using localstorage (local, manual) // can't use storage during bootstrap as the localstorage worker // isn't running. Use filestorage instead. var stor storage.Storage storageDir := agentConfig.Value(agent.StorageDir) if storageDir != "" { stor, err = filestorage.NewFileStorageWriter(storageDir) if err != nil { return err } } else { stor = env.Storage() } // Create a temporary directory to contain source and cloned tools. tempDir, err := ioutil.TempDir("", "juju-sync-tools") if err != nil { return err } defer os.RemoveAll(tempDir) destTools := filepath.Join(tempDir, filepath.FromSlash(envtools.StorageName(tools.Version))) if err := os.MkdirAll(filepath.Dir(destTools), 0700); err != nil { return err } srcTools := filepath.Join( agenttools.SharedToolsDir(dataDir, version.Current), "tools.tar.gz", ) if err := utils.CopyFile(destTools, srcTools); err != nil { return err } // Until we catalogue tools in state, we clone the tools // for each of the supported series of the same OS. otherSeries := version.OSSupportedSeries(version.Current.OS) _, err = sync.SyncBuiltTools(stor, &sync.BuiltTools{ Version: tools.Version, Dir: tempDir, StorageName: envtools.StorageName(tools.Version), Sha256Hash: tools.SHA256, Size: tools.Size, }, otherSeries...) return err }
// 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 }
// 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, ) ( *environs.StartInstanceResult, error, ) { if err := fillinStartInstanceParams(env, machineId, false, ¶ms); err != nil { return nil, err } return env.StartInstance(params) }
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 validateConstraints(env environs.Environ, cons constraints.Value) error { validator, err := env.ConstraintsValidator() if err != nil { return err } unsupported, err := validator.Validate(cons) if len(unsupported) > 0 { logger.Warningf("unsupported constraints: %v", unsupported) } return err }
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 }
// EnsureNotBootstrapped returns nil if the environment is not // bootstrapped, and an error if it is or if the function was not able // to tell. func EnsureNotBootstrapped(env environs.Environ) error { _, err := env.StateServerInstances() // If there is no error determining state server instaces, // then we are bootstrapped. if err == nil { return environs.ErrAlreadyBootstrapped } if err == environs.ErrNotBootstrapped { return nil } return err }
// AssertStartControllerInstance is a test helper function that starts a // controller instance with a plausible but invalid configuration, and // checks that it succeeds. func AssertStartControllerInstance( c *gc.C, env environs.Environ, controllerUUID, machineId string, ) ( instance.Instance, *instance.HardwareCharacteristics, ) { params := environs.StartInstanceParams{ControllerUUID: controllerUUID} err := fillinStartInstanceParams(env, machineId, true, ¶ms) c.Assert(err, jc.ErrorIsNil) result, err := env.StartInstance(params) c.Assert(err, jc.ErrorIsNil) return result.Instance, result.Hardware }
// EnsureNotBootstrapped returns nil if the environment is not // bootstrapped, and an error if it is or if the function was not able // to tell. func EnsureNotBootstrapped(env environs.Environ) error { _, err := LoadState(env.Storage()) // If there is no error loading the bootstrap state, then we are // bootstrapped. if err == nil { return environs.ErrAlreadyBootstrapped } if err == environs.ErrNotBootstrapped { return nil } return err }
// 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 }
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") } }