func (s *configureSuite) getCloudConfig(c *gc.C, stateServer bool, vers version.Binary) cloudinit.CloudConfig { var icfg *instancecfg.InstanceConfig var err error if stateServer { icfg, err = instancecfg.NewBootstrapInstanceConfig(constraints.Value{}, vers.Series) c.Assert(err, jc.ErrorIsNil) icfg.InstanceId = "instance-id" icfg.Jobs = []multiwatcher.MachineJob{multiwatcher.JobManageEnviron, multiwatcher.JobHostUnits} } else { icfg, err = instancecfg.NewInstanceConfig("0", "ya", imagemetadata.ReleasedStream, vers.Series, true, nil, nil, nil) c.Assert(err, jc.ErrorIsNil) icfg.Jobs = []multiwatcher.MachineJob{multiwatcher.JobHostUnits} } icfg.Tools = &tools.Tools{ Version: vers, URL: "http://testing.invalid/tools.tar.gz", } environConfig := testConfig(c, stateServer, vers) err = instancecfg.FinishInstanceConfig(icfg, environConfig) c.Assert(err, jc.ErrorIsNil) cloudcfg, err := cloudinit.New(icfg.Series) c.Assert(err, jc.ErrorIsNil) udata, err := cloudconfig.NewUserdataConfig(icfg, cloudcfg) c.Assert(err, jc.ErrorIsNil) err = udata.Configure() c.Assert(err, jc.ErrorIsNil) return cloudcfg }
func (s *configureSuite) getCloudConfig(c *gc.C, controller bool, vers version.Binary) cloudinit.CloudConfig { var icfg *instancecfg.InstanceConfig var err error modelConfig := testConfig(c, controller, vers) if controller { icfg, err = instancecfg.NewBootstrapInstanceConfig( coretesting.FakeControllerConfig(), constraints.Value{}, constraints.Value{}, vers.Series, "", ) c.Assert(err, jc.ErrorIsNil) icfg.APIInfo = &api.Info{ Password: "******", CACert: coretesting.CACert, ModelTag: coretesting.ModelTag, } icfg.Controller.MongoInfo = &mongo.MongoInfo{ Password: "******", Info: mongo.Info{CACert: coretesting.CACert}, } icfg.Bootstrap.ControllerModelConfig = modelConfig icfg.Bootstrap.BootstrapMachineInstanceId = "instance-id" icfg.Bootstrap.HostedModelConfig = map[string]interface{}{ "name": "hosted-model", } icfg.Bootstrap.StateServingInfo = params.StateServingInfo{ Cert: coretesting.ServerCert, PrivateKey: coretesting.ServerKey, CAPrivateKey: coretesting.CAKey, StatePort: 123, APIPort: 456, } icfg.Jobs = []multiwatcher.MachineJob{multiwatcher.JobManageModel, multiwatcher.JobHostUnits} icfg.Bootstrap.StateServingInfo = params.StateServingInfo{ Cert: coretesting.ServerCert, PrivateKey: coretesting.ServerKey, CAPrivateKey: coretesting.CAKey, StatePort: 123, APIPort: 456, } } else { icfg, err = instancecfg.NewInstanceConfig(coretesting.ControllerTag, "0", "ya", imagemetadata.ReleasedStream, vers.Series, nil) c.Assert(err, jc.ErrorIsNil) icfg.Jobs = []multiwatcher.MachineJob{multiwatcher.JobHostUnits} } err = icfg.SetTools(tools.List{ &tools.Tools{ Version: vers, URL: "http://testing.invalid/tools.tar.gz", }, }) err = instancecfg.FinishInstanceConfig(icfg, modelConfig) c.Assert(err, jc.ErrorIsNil) cloudcfg, err := cloudinit.New(icfg.Series) c.Assert(err, jc.ErrorIsNil) udata, err := cloudconfig.NewUserdataConfig(icfg, cloudcfg) c.Assert(err, jc.ErrorIsNil) err = udata.Configure() c.Assert(err, jc.ErrorIsNil) return cloudcfg }
func (s *localJujuTestSuite) testBootstrap(c *gc.C, cfg *config.Config) environs.Environ { ctx := envtesting.BootstrapContext(c) environ, err := local.Provider.PrepareForBootstrap(ctx, cfg) c.Assert(err, jc.ErrorIsNil) availableTools := coretools.List{&coretools.Tools{ Version: version.Binary{ Number: version.Current, Arch: arch.HostArch(), Series: series.HostSeries(), }, URL: "http://testing.invalid/tools.tar.gz", }} result, err := environ.Bootstrap(ctx, environs.BootstrapParams{ AvailableTools: availableTools, }) c.Assert(err, jc.ErrorIsNil) icfg, err := instancecfg.NewBootstrapInstanceConfig( constraints.Value{}, constraints.Value{}, "quantal", "", ) c.Assert(err, jc.ErrorIsNil) icfg.Tools = availableTools[0] err = result.Finalize(ctx, icfg) c.Assert(err, jc.ErrorIsNil) return environ }
func (s *BaseSuiteUnpatched) initInst(c *gc.C) { tools := []*tools.Tools{{ Version: version.Binary{Arch: arch.AMD64, Series: "trusty"}, URL: "https://example.org", }} cons := constraints.Value{InstanceType: &allInstanceTypes[0].Name} instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(cons, cons, "trusty", "") c.Assert(err, jc.ErrorIsNil) instanceConfig.Tools = tools[0] instanceConfig.AuthorizedKeys = s.Config.AuthorizedKeys() userData, err := providerinit.ComposeUserData(instanceConfig, nil, GCERenderer{}) c.Assert(err, jc.ErrorIsNil) authKeys, err := google.FormatAuthorizedKeys(instanceConfig.AuthorizedKeys, "ubuntu") c.Assert(err, jc.ErrorIsNil) s.UbuntuMetadata = map[string]string{ metadataKeyIsState: metadataValueTrue, metadataKeyCloudInit: string(userData), metadataKeyEncoding: "base64", metadataKeySSHKeys: authKeys, } s.WindowsMetadata = map[string]string{ metadataKeyWindowsUserdata: string(userData), metadataKeyWindowsSysprep: fmt.Sprintf(winSetHostnameScript, "juju.*"), } s.Addresses = []network.Address{{ Value: "10.0.0.1", Type: network.IPv4Address, Scope: network.ScopeCloudLocal, }} s.Instance = s.NewInstance(c, "spam") s.BaseInstance = s.Instance.base s.InstName = s.Prefix + "machine-spam" s.StartInstArgs = environs.StartInstanceParams{ InstanceConfig: instanceConfig, Tools: tools, Constraints: cons, //Placement: "", //DistributionGroup: nil, } s.InstanceType = allInstanceTypes[0] // Storage eUUID := s.Env.Config().UUID() s.BaseDisk = &google.Disk{ Id: 1234567, Name: "home-zone--c930380d-8337-4bf5-b07a-9dbb5ae771e4", Zone: "home-zone", Status: google.StatusReady, Size: 1024, Description: eUUID, } }
func (s *BootstrapSuite) TestCannotStartInstance(c *gc.C) { s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) checkPlacement := "directive" checkCons := constraints.MustParse("mem=8G") env := &mockEnviron{ storage: newStorage(s, c), config: configGetter(c), } startInstance := func( placement string, cons constraints.Value, _ []string, possibleTools tools.List, icfg *instancecfg.InstanceConfig, ) (instance.Instance, *instance.HardwareCharacteristics, []network.InterfaceInfo, error) { c.Assert(placement, gc.DeepEquals, checkPlacement) c.Assert(cons, gc.DeepEquals, checkCons) // The machine config should set its upgrade behavior based on // the environment config. expectedMcfg, err := instancecfg.NewBootstrapInstanceConfig(coretesting.FakeControllerConfig(), cons, cons, icfg.Series, "") c.Assert(err, jc.ErrorIsNil) expectedMcfg.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() expectedMcfg.EnableOSUpgrade = env.Config().EnableOSUpgrade() expectedMcfg.Tags = map[string]string{ "juju-model-uuid": coretesting.ModelTag.Id(), "juju-controller-uuid": coretesting.ControllerTag.Id(), "juju-is-controller": "true", } c.Assert(icfg, jc.DeepEquals, expectedMcfg) return nil, nil, nil, errors.Errorf("meh, not started") } env.startInstance = startInstance ctx := envtesting.BootstrapContext(c) _, err := common.Bootstrap(ctx, env, environs.BootstrapParams{ ControllerConfig: coretesting.FakeControllerConfig(), BootstrapConstraints: checkCons, ModelConstraints: checkCons, Placement: checkPlacement, AvailableTools: tools.List{ &tools.Tools{ Version: version.Binary{ Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), }, }, }}) c.Assert(err, gc.ErrorMatches, "cannot start bootstrap instance: meh, not started") }
func (s *BaseSuiteUnpatched) initInst(c *gc.C) { tools := []*tools.Tools{{ Version: version.Binary{Arch: arch.AMD64, Series: "trusty"}, URL: "https://example.org", }} cons := constraints.Value{ // nothing } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(cons, cons, "trusty", "") c.Assert(err, jc.ErrorIsNil) instanceConfig.Tools = tools[0] instanceConfig.AuthorizedKeys = s.Config.AuthorizedKeys() userData, err := providerinit.ComposeUserData(instanceConfig, nil, lxdRenderer{}) c.Assert(err, jc.ErrorIsNil) s.Hardware = &lxdclient.InstanceHardware{ Architecture: arch.AMD64, NumCores: 1, MemoryMB: 3750, } var archName string = arch.AMD64 var numCores uint64 = 1 var memoryMB uint64 = 3750 s.HWC = &instance.HardwareCharacteristics{ Arch: &archName, CpuCores: &numCores, Mem: &memoryMB, } s.Metadata = map[string]string{ // userdata metadataKeyIsState: metadataValueTrue, // bootstrap metadataKeyCloudInit: string(userData), } s.Addresses = []network.Address{{ Value: "10.0.0.1", Type: network.IPv4Address, Scope: network.ScopeCloudLocal, }} s.Instance = s.NewInstance(c, "spam") s.RawInstance = s.Instance.raw s.InstName = s.Prefix + "machine-spam" s.StartInstArgs = environs.StartInstanceParams{ InstanceConfig: instanceConfig, Tools: tools, Constraints: cons, } }
func (s *BaseSuiteUnpatched) initInst(c *gc.C) { tools := []*tools.Tools{{ Version: version.Binary{Arch: arch.AMD64, Series: "trusty"}, URL: "https://example.org", }} cons := constraints.Value{InstanceType: &allInstanceTypes[0].Name} instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(cons, "trusty") c.Assert(err, jc.ErrorIsNil) instanceConfig.Tools = tools[0] instanceConfig.AuthorizedKeys = s.Config.AuthorizedKeys() userData, err := providerinit.ComposeUserData(instanceConfig, nil) c.Assert(err, jc.ErrorIsNil) b64UserData := base64.StdEncoding.EncodeToString([]byte(userData)) authKeys, err := google.FormatAuthorizedKeys(instanceConfig.AuthorizedKeys, "ubuntu") c.Assert(err, jc.ErrorIsNil) s.Metadata = map[string]string{ metadataKeyIsState: metadataValueTrue, metadataKeyCloudInit: b64UserData, metadataKeyEncoding: "base64", metadataKeySSHKeys: authKeys, } s.Addresses = []network.Address{{ Value: "10.0.0.1", Type: network.IPv4Address, Scope: network.ScopeCloudLocal, }} s.Instance = s.NewInstance(c, "spam") s.BaseInstance = s.Instance.base s.InstName = s.Prefix + "machine-spam" s.StartInstArgs = environs.StartInstanceParams{ InstanceConfig: instanceConfig, Tools: tools, Constraints: cons, //Placement: "", //DistributionGroup: nil, } s.InstanceType = allInstanceTypes[0] }
func (s *localJujuTestSuite) testBootstrap(c *gc.C, cfg *config.Config) environs.Environ { ctx := envtesting.BootstrapContext(c) environ, err := local.Provider.PrepareForBootstrap(ctx, cfg) c.Assert(err, jc.ErrorIsNil) availableTools := coretools.List{&coretools.Tools{ Version: version.Current, URL: "http://testing.invalid/tools.tar.gz", }} _, _, finalizer, err := environ.Bootstrap(ctx, environs.BootstrapParams{ AvailableTools: availableTools, }) c.Assert(err, jc.ErrorIsNil) icfg, err := instancecfg.NewBootstrapInstanceConfig(constraints.Value{}, "quantal") c.Assert(err, jc.ErrorIsNil) icfg.Tools = availableTools[0] err = finalizer(ctx, icfg) c.Assert(err, jc.ErrorIsNil) return environ }
func (s *environBrokerSuite) CreateStartInstanceArgs(c *gc.C) environs.StartInstanceParams { tools := []*tools.Tools{{ Version: version.Binary{Arch: arch.AMD64, Series: "trusty"}, URL: "https://example.org", }} cons := constraints.Value{} instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(cons, cons, "trusty", "") c.Assert(err, jc.ErrorIsNil) instanceConfig.Tools = tools[0] instanceConfig.AuthorizedKeys = s.Config.AuthorizedKeys() return environs.StartInstanceParams{ InstanceConfig: instanceConfig, Tools: tools, Constraints: cons, } }
// 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 BootstrapParams) error { cfg := environ.Config() network.InitializeFromConfig(cfg) if secret := cfg.AdminSecret(); secret == "" { return errors.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 errors.Errorf("environment configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return errors.Errorf("environment configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return errors.Errorf("environment configuration has no ca-private-key") } // Set default tools metadata source, add image metadata source, // then verify constraints. Providers may rely on image metadata // for constraint validation. var imageMetadata []*imagemetadata.ImageMetadata if args.MetadataDir != "" { var err error imageMetadata, err = setPrivateMetadataSources(environ, args.MetadataDir) if err != nil { return err } } if err := validateConstraints(environ, args.Constraints); err != nil { return err } _, supportsNetworking := environs.SupportsNetworking(environ) ctx.Infof("Bootstrapping environment %q", cfg.Name()) logger.Debugf("environment %q supports service/machine networks: %v", cfg.Name(), supportsNetworking) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement) availableTools, err := findAvailableTools(environ, args.AgentVersion, args.Constraints.Arch, args.UploadTools) if errors.IsNotFound(err) { return errors.New(noToolsMessage) } else if err != nil { return err } if lxcMTU, ok := cfg.LXCDefaultMTU(); ok { logger.Debugf("using MTU %v for all created LXC containers' network interfaces", lxcMTU) } // If we're uploading, we must override agent-version; // if we're not uploading, we want to ensure we have an // agent-version set anyway, to appease FinishInstanceConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. agentVersion := version.Current if args.AgentVersion != nil { agentVersion = *args.AgentVersion } if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": agentVersion.String(), }); err != nil { return err } if err = environ.SetConfig(cfg); err != nil { return err } ctx.Infof("Starting new instance for initial state server") arch, series, finalizer, err := environ.Bootstrap(ctx, environs.BootstrapParams{ Constraints: args.Constraints, Placement: args.Placement, AvailableTools: availableTools, }) if err != nil { return err } matchingTools, err := availableTools.Match(coretools.Filter{ Arch: arch, Series: series, }) if err != nil { return err } selectedTools, err := setBootstrapTools(environ, matchingTools) if err != nil { return err } if selectedTools.URL == "" { if !args.UploadTools { logger.Warningf("no prepackaged tools available") } ctx.Infof("Building tools to upload (%s)", selectedTools.Version) builtTools, err := sync.BuildToolsTarball(&selectedTools.Version.Number, cfg.AgentStream()) if err != nil { return errors.Annotate(err, "cannot upload bootstrap tools") } defer os.RemoveAll(builtTools.Dir) filename := filepath.Join(builtTools.Dir, builtTools.StorageName) selectedTools.URL = fmt.Sprintf("file://%s", filename) selectedTools.Size = builtTools.Size selectedTools.SHA256 = builtTools.Sha256Hash } ctx.Infof("Installing Juju agent on bootstrap instance") instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(args.Constraints, series) if err != nil { return err } instanceConfig.Tools = selectedTools instanceConfig.CustomImageMetadata = imageMetadata if err := finalizer(ctx, instanceConfig); err != nil { return err } ctx.Infof("Bootstrap agent installed") return nil }
// BootstrapInstance creates a new instance with the series of its choice, // constrained to those of the available tools, and // returns the instance result, series, and a function that // must be called to finalize the bootstrap process by transferring // the tools and installing the initial Juju controller. // This method is called by Bootstrap above, which implements environs.Bootstrap, but // is also exported so that providers can manipulate the started instance. func BootstrapInstance(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams, ) (_ *environs.StartInstanceResult, selectedSeries 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. // First thing, ensure we have tools otherwise there's no point. if args.BootstrapSeries != "" { selectedSeries = args.BootstrapSeries } else { selectedSeries = config.PreferredSeries(env.Config()) } availableTools, err := args.AvailableTools.Match(coretools.Filter{ Series: selectedSeries, }) if err != nil { return nil, "", nil, err } // Filter image metadata to the selected series. var imageMetadata []*imagemetadata.ImageMetadata seriesVersion, err := series.SeriesVersion(selectedSeries) if err != nil { return nil, "", nil, errors.Trace(err) } for _, m := range args.ImageMetadata { if m.Version != seriesVersion { continue } imageMetadata = append(imageMetadata, m) } // 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, "", nil, fmt.Errorf("no SSH client available") } publicKey, err := simplestreams.UserPublicSigningKey() if err != nil { return nil, "", nil, err } envCfg := env.Config() instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.ControllerConfig, args.BootstrapConstraints, args.ModelConstraints, selectedSeries, publicKey, ) if err != nil { return nil, "", nil, err } instanceConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() instanceConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() instanceConfig.Tags = instancecfg.InstanceTags(envCfg.UUID(), args.ControllerConfig.ControllerUUID(), envCfg, instanceConfig.Jobs) maybeSetBridge := func(icfg *instancecfg.InstanceConfig) { // If we need to override the default bridge name, do it now. When // args.ContainerBridgeName is empty, the default names for LXC // (lxcbr0) and KVM (virbr0) will be used. if args.ContainerBridgeName != "" { logger.Debugf("using %q as network bridge for all container types", args.ContainerBridgeName) if icfg.AgentEnvironment == nil { icfg.AgentEnvironment = make(map[string]string) } icfg.AgentEnvironment[agent.LxcBridge] = args.ContainerBridgeName } } maybeSetBridge(instanceConfig) cloudRegion := args.CloudName if args.CloudRegion != "" { cloudRegion += "/" + args.CloudRegion } fmt.Fprintf(ctx.GetStderr(), "Launching controller instance(s) on %s...\n", cloudRegion) // Print instance status reports status changes during provisioning. // Note the carriage returns, meaning subsequent prints are to the same // line of stderr, not a new line. instanceStatus := func(settableStatus status.Status, info string, data map[string]interface{}) error { // The data arg is not expected to be used in this case, but // print it, rather than ignore it, if we get something. dataString := "" if len(data) > 0 { dataString = fmt.Sprintf(" %v", data) } fmt.Fprintf(ctx.GetStderr(), " - %s%s\r", info, dataString) return nil } // Likely used after the final instanceStatus call to white-out the // current stderr line before the next use, removing any residual status // reporting output. statusCleanup := func(info string) error { // The leading spaces account for the leading characters // emitted by instanceStatus above. fmt.Fprintf(ctx.GetStderr(), " %s\r", info) return nil } result, err := env.StartInstance(environs.StartInstanceParams{ ControllerUUID: args.ControllerConfig.ControllerUUID(), Constraints: args.BootstrapConstraints, Tools: availableTools, InstanceConfig: instanceConfig, Placement: args.Placement, ImageMetadata: imageMetadata, StatusCallback: instanceStatus, CleanupCallback: statusCleanup, }) if err != nil { return nil, "", nil, errors.Annotate(err, "cannot start bootstrap instance") } // We need some padding below to overwrite any previous messages. We'll use a width of 40. msg := fmt.Sprintf(" - %s", result.Instance.Id()) if len(msg) < 40 { padding := make([]string, 40-len(msg)) msg += strings.Join(padding, " ") } fmt.Fprintln(ctx.GetStderr(), msg) finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig, opts environs.BootstrapDialOpts) error { icfg.Bootstrap.BootstrapMachineInstanceId = result.Instance.Id() icfg.Bootstrap.BootstrapMachineHardwareCharacteristics = result.Hardware envConfig := env.Config() if result.Config != nil { updated, err := envConfig.Apply(result.Config.UnknownAttrs()) if err != nil { return errors.Trace(err) } envConfig = updated } if err := instancecfg.FinishInstanceConfig(icfg, envConfig); err != nil { return err } maybeSetBridge(icfg) return FinishBootstrap(ctx, client, env, result.Instance, icfg, opts) } return result, selectedSeries, finalize, 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 BootstrapParams) error { cfg := environ.Config() network.SetPreferIPv6(cfg.PreferIPv6()) if secret := cfg.AdminSecret(); secret == "" { return errors.Errorf("model 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 errors.Errorf("model configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return errors.Errorf("model configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return errors.Errorf("model configuration has no ca-private-key") } // Set default tools metadata source, add image metadata source, // then verify constraints. Providers may rely on image metadata // for constraint validation. var customImageMetadata []*imagemetadata.ImageMetadata if args.MetadataDir != "" { var err error customImageMetadata, err = setPrivateMetadataSources(environ, args.MetadataDir) if err != nil { return err } } if err := validateConstraints(environ, args.ModelConstraints); err != nil { return err } if err := validateConstraints(environ, args.BootstrapConstraints); err != nil { return err } constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return err } bootstrapConstraints, err := constraintsValidator.Merge( args.ModelConstraints, args.BootstrapConstraints, ) if err != nil { return err } _, supportsNetworking := environs.SupportsNetworking(environ) var bootstrapSeries *string if args.BootstrapSeries != "" { bootstrapSeries = &args.BootstrapSeries } ctx.Infof("Bootstrapping model %q", cfg.Name()) logger.Debugf("model %q supports service/machine networks: %v", cfg.Name(), supportsNetworking) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement) availableTools, err := findAvailableTools( environ, args.AgentVersion, bootstrapConstraints.Arch, bootstrapSeries, args.UploadTools, args.BuildToolsTarball != nil, ) if errors.IsNotFound(err) { return errors.New(noToolsMessage) } else if err != nil { return err } if lxcMTU, ok := cfg.LXCDefaultMTU(); ok { logger.Debugf("using MTU %v for all created LXC containers' network interfaces", lxcMTU) } imageMetadata, err := bootstrapImageMetadata( environ, availableTools, args.BootstrapImage, &customImageMetadata, ) if err != nil { return errors.Trace(err) } // If we're uploading, we must override agent-version; // if we're not uploading, we want to ensure we have an // agent-version set anyway, to appease FinishInstanceConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. agentVersion := jujuversion.Current if args.AgentVersion != nil { agentVersion = *args.AgentVersion } if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": agentVersion.String(), }); err != nil { return err } if err = environ.SetConfig(cfg); err != nil { return err } ctx.Infof("Starting new instance for initial controller") result, err := environ.Bootstrap(ctx, environs.BootstrapParams{ ModelConstraints: args.ModelConstraints, BootstrapConstraints: args.BootstrapConstraints, BootstrapSeries: args.BootstrapSeries, Placement: args.Placement, AvailableTools: availableTools, ImageMetadata: imageMetadata, }) if err != nil { return err } matchingTools, err := availableTools.Match(coretools.Filter{ Arch: result.Arch, Series: result.Series, }) if err != nil { return err } selectedToolsList, err := setBootstrapTools(environ, matchingTools) if err != nil { return err } havePrepackaged := false for i, selectedTools := range selectedToolsList { if selectedTools.URL != "" { havePrepackaged = true continue } ctx.Infof("Building tools to upload (%s)", selectedTools.Version) builtTools, err := args.BuildToolsTarball(&selectedTools.Version.Number, cfg.AgentStream()) if err != nil { return errors.Annotate(err, "cannot upload bootstrap tools") } defer os.RemoveAll(builtTools.Dir) filename := filepath.Join(builtTools.Dir, builtTools.StorageName) selectedTools.URL = fmt.Sprintf("file://%s", filename) selectedTools.Size = builtTools.Size selectedTools.SHA256 = builtTools.Sha256Hash selectedToolsList[i] = selectedTools } if !havePrepackaged && !args.UploadTools { // There are no prepackaged agents, so we must upload // even though the user didn't ask for it. We only do // this when the image-stream is not "released" and // the agent version hasn't been specified. logger.Warningf("no prepackaged tools available") } ctx.Infof("Installing Juju agent on bootstrap instance") publicKey, err := userPublicSigningKey() if err != nil { return err } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.BootstrapConstraints, args.ModelConstraints, result.Series, publicKey, ) if err != nil { return err } if err := instanceConfig.SetTools(selectedToolsList); err != nil { return errors.Trace(err) } instanceConfig.CustomImageMetadata = customImageMetadata instanceConfig.HostedModelConfig = args.HostedModelConfig instanceConfig.GUI = guiArchive(args.GUIDataSourceBaseURL, func(msg string) { ctx.Infof(msg) }) if err := result.Finalize(ctx, instanceConfig); err != nil { return err } ctx.Infof("Bootstrap agent installed") return nil }
// BootstrapInstance creates a new instance with the series and architecture // of its choice, constrained to those of the available tools, and // returns the instance result, series, and a function that // must be called to finalize the bootstrap process by transferring // the tools and installing the initial Juju state server. // This method is called by Bootstrap above, which implements environs.Bootstrap, but // is also exported so that providers can manipulate the started instance. func BootstrapInstance(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams, ) (_ *environs.StartInstanceResult, 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. // 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, "", 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, "", nil, fmt.Errorf("no SSH client available") } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(args.Constraints, series) if err != nil { return nil, "", nil, err } instanceConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() instanceConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() instanceConfig.Tags = instancecfg.InstanceTags(env.Config(), instanceConfig.Jobs) maybeSetBridge := func(icfg *instancecfg.InstanceConfig) { // If we need to override the default bridge name, do it now. When // args.ContainerBridgeName is empty, the default names for LXC // (lxcbr0) and KVM (virbr0) will be used. if args.ContainerBridgeName != "" { logger.Debugf("using %q as network bridge for all container types", args.ContainerBridgeName) if icfg.AgentEnvironment == nil { icfg.AgentEnvironment = make(map[string]string) } icfg.AgentEnvironment[agent.LxcBridge] = args.ContainerBridgeName } } maybeSetBridge(instanceConfig) fmt.Fprintln(ctx.GetStderr(), "Launching instance") result, err := env.StartInstance(environs.StartInstanceParams{ Constraints: args.Constraints, Tools: availableTools, InstanceConfig: instanceConfig, Placement: args.Placement, }) if err != nil { return nil, "", nil, errors.Annotate(err, "cannot start bootstrap instance") } fmt.Fprintf(ctx.GetStderr(), " - %s\n", result.Instance.Id()) finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { icfg.InstanceId = result.Instance.Id() icfg.HardwareCharacteristics = result.Hardware if err := instancecfg.FinishInstanceConfig(icfg, env.Config()); err != nil { return err } maybeSetBridge(icfg) return FinishBootstrap(ctx, client, result.Instance, icfg) } return result, series, finalize, nil }
// BootstrapInstance creates a new instance with the series and architecture // of its choice, constrained to those of the available tools, and // returns the instance result, series, and a function that // must be called to finalize the bootstrap process by transferring // the tools and installing the initial Juju controller. // This method is called by Bootstrap above, which implements environs.Bootstrap, but // is also exported so that providers can manipulate the started instance. func BootstrapInstance(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams, ) (_ *environs.StartInstanceResult, selectedSeries 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. // First thing, ensure we have tools otherwise there's no point. if args.BootstrapSeries != "" { selectedSeries = args.BootstrapSeries } else { selectedSeries = config.PreferredSeries(env.Config()) } availableTools, err := args.AvailableTools.Match(coretools.Filter{ Series: selectedSeries, }) if err != nil { return nil, "", nil, err } // Filter image metadata to the selected series. var imageMetadata []*imagemetadata.ImageMetadata seriesVersion, err := series.SeriesVersion(selectedSeries) if err != nil { return nil, "", nil, errors.Trace(err) } for _, m := range args.ImageMetadata { if m.Version != seriesVersion { continue } imageMetadata = append(imageMetadata, m) } // 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, "", nil, fmt.Errorf("no SSH client available") } publicKey, err := simplestreams.UserPublicSigningKey() if err != nil { return nil, "", nil, err } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.BootstrapConstraints, args.ModelConstraints, selectedSeries, publicKey, ) if err != nil { return nil, "", nil, err } instanceConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() instanceConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() instanceConfig.Tags = instancecfg.InstanceTags(env.Config(), instanceConfig.Jobs) maybeSetBridge := func(icfg *instancecfg.InstanceConfig) { // If we need to override the default bridge name, do it now. When // args.ContainerBridgeName is empty, the default names for LXC // (lxcbr0) and KVM (virbr0) will be used. if args.ContainerBridgeName != "" { logger.Debugf("using %q as network bridge for all container types", args.ContainerBridgeName) if icfg.AgentEnvironment == nil { icfg.AgentEnvironment = make(map[string]string) } icfg.AgentEnvironment[agent.LxcBridge] = args.ContainerBridgeName } } maybeSetBridge(instanceConfig) fmt.Fprintln(ctx.GetStderr(), "Launching instance") instanceStatus := func(settableStatus status.Status, info string, data map[string]interface{}) error { fmt.Fprintf(ctx.GetStderr(), "%s \r", info) return nil } result, err := env.StartInstance(environs.StartInstanceParams{ Constraints: args.BootstrapConstraints, Tools: availableTools, InstanceConfig: instanceConfig, Placement: args.Placement, ImageMetadata: imageMetadata, StatusCallback: instanceStatus, }) if err != nil { return nil, "", nil, errors.Annotate(err, "cannot start bootstrap instance") } fmt.Fprintf(ctx.GetStderr(), " - %s\n", result.Instance.Id()) finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { icfg.InstanceId = result.Instance.Id() icfg.HardwareCharacteristics = result.Hardware envConfig := env.Config() if result.Config != nil { updated, err := envConfig.Apply(result.Config.UnknownAttrs()) if err != nil { return errors.Trace(err) } envConfig = updated } if err := instancecfg.FinishInstanceConfig(icfg, envConfig); err != nil { return err } maybeSetBridge(icfg) return FinishBootstrap(ctx, client, env, result.Instance, icfg) } return result, selectedSeries, finalize, 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 BootstrapParams) error { if err := args.Validate(); err != nil { return errors.Annotate(err, "validating bootstrap parameters") } cfg := environ.Config() 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 errors.Errorf("model configuration has no authorized-keys") } _, supportsNetworking := environs.SupportsNetworking(environ) logger.Debugf("model %q supports service/machine networks: %v", cfg.Name(), supportsNetworking) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement) // Set default tools metadata source, add image metadata source, // then verify constraints. Providers may rely on image metadata // for constraint validation. var customImageMetadata []*imagemetadata.ImageMetadata if args.MetadataDir != "" { var err error customImageMetadata, err = setPrivateMetadataSources(args.MetadataDir) if err != nil { return err } } var bootstrapSeries *string if args.BootstrapSeries != "" { bootstrapSeries = &args.BootstrapSeries } var bootstrapArchForImageSearch string if args.BootstrapConstraints.Arch != nil { bootstrapArchForImageSearch = *args.BootstrapConstraints.Arch } else if args.ModelConstraints.Arch != nil { bootstrapArchForImageSearch = *args.ModelConstraints.Arch } else { bootstrapArchForImageSearch = arch.HostArch() // We no longer support i386. if bootstrapArchForImageSearch == arch.I386 { bootstrapArchForImageSearch = arch.AMD64 } } ctx.Verbosef("Loading image metadata") imageMetadata, err := bootstrapImageMetadata(environ, bootstrapSeries, bootstrapArchForImageSearch, args.BootstrapImage, &customImageMetadata, ) if err != nil { return errors.Trace(err) } // We want to determine a list of valid architectures for which to pick tools and images. // This includes architectures from custom and other available image metadata. architectures := set.NewStrings() if len(customImageMetadata) > 0 { for _, customMetadata := range customImageMetadata { architectures.Add(customMetadata.Arch) } } if len(imageMetadata) > 0 { for _, iMetadata := range imageMetadata { architectures.Add(iMetadata.Arch) } } constraintsValidator, err := environ.ConstraintsValidator() if err != nil { return err } constraintsValidator.UpdateVocabulary(constraints.Arch, architectures.SortedValues()) bootstrapConstraints, err := constraintsValidator.Merge( args.ModelConstraints, args.BootstrapConstraints, ) if err != nil { return errors.Trace(err) } // The arch we use to find tools isn't the boostrapConstraints arch. // We copy the constraints arch to a separate variable and // update it from the host arch if not specified. // (axw) This is still not quite right: // For e.g. if there is a MAAS with only ARM64 machines, // on an AMD64 client, we're going to look for only AMD64 tools, // limiting what the provider can bootstrap anyway. var bootstrapArch string if bootstrapConstraints.Arch != nil { bootstrapArch = *bootstrapConstraints.Arch } else { // If no arch is specified as a constraint, we'll bootstrap // on the same arch as the client used to bootstrap. bootstrapArch = arch.HostArch() // We no longer support controllers on i386. // If we are bootstrapping from an i386 client, // we'll look for amd64 tools. if bootstrapArch == arch.I386 { bootstrapArch = arch.AMD64 } } var availableTools coretools.List if !args.BuildAgent { ctx.Infof("Looking for packaged Juju agent version %s for %s", args.AgentVersion, bootstrapArch) availableTools, err = findPackagedTools(environ, args.AgentVersion, &bootstrapArch, bootstrapSeries) if err != nil && !errors.IsNotFound(err) { return err } } // If there are no prepackaged tools and a specific version has not been // requested, look for or build a local binary. var builtTools *sync.BuiltAgent if len(availableTools) == 0 && (args.AgentVersion == nil || isCompatibleVersion(*args.AgentVersion, jujuversion.Current)) { if args.BuildAgentTarball == nil { return errors.New("cannot build agent binary to upload") } if err := validateUploadAllowed(environ, &bootstrapArch, bootstrapSeries, constraintsValidator); err != nil { return err } if args.BuildAgent { ctx.Infof("Building local Juju agent binary version %s for %s", args.AgentVersion, bootstrapArch) } else { ctx.Infof("No packaged binary found, preparing local Juju agent binary") } var forceVersion version.Number availableTools, forceVersion = locallyBuildableTools(bootstrapSeries) builtTools, err = args.BuildAgentTarball(args.BuildAgent, &forceVersion, cfg.AgentStream()) if err != nil { return errors.Annotate(err, "cannot package bootstrap agent binary") } defer os.RemoveAll(builtTools.Dir) for i, tool := range availableTools { if tool.URL != "" { continue } filename := filepath.Join(builtTools.Dir, builtTools.StorageName) tool.URL = fmt.Sprintf("file://%s", filename) tool.Size = builtTools.Size tool.SHA256 = builtTools.Sha256Hash availableTools[i] = tool } } if len(availableTools) == 0 { return errors.New(noToolsMessage) } // If we're uploading, we must override agent-version; // if we're not uploading, we want to ensure we have an // agent-version set anyway, to appease FinishInstanceConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. agentVersion := jujuversion.Current if args.AgentVersion != nil { agentVersion = *args.AgentVersion } if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": agentVersion.String(), }); err != nil { return err } if err = environ.SetConfig(cfg); err != nil { return err } ctx.Verbosef("Starting new instance for initial controller") result, err := environ.Bootstrap(ctx, environs.BootstrapParams{ CloudName: args.CloudName, CloudRegion: args.CloudRegion, ControllerConfig: args.ControllerConfig, ModelConstraints: args.ModelConstraints, BootstrapConstraints: bootstrapConstraints, BootstrapSeries: args.BootstrapSeries, Placement: args.Placement, AvailableTools: availableTools, ImageMetadata: imageMetadata, }) if err != nil { return err } matchingTools, err := availableTools.Match(coretools.Filter{ Arch: result.Arch, Series: result.Series, }) if err != nil { return err } selectedToolsList, err := getBootstrapToolsVersion(matchingTools) if err != nil { return err } // We set agent-version to the newest version, so the agent will immediately upgrade itself. // Note that this only is relevant if a specific agent version has not been requested, since // in that case the specific version will be the only version available. newestVersion, _ := matchingTools.Newest() if err := setBootstrapToolsVersion(environ, newestVersion); err != nil { return err } logger.Infof("Installing Juju agent on bootstrap instance") publicKey, err := userPublicSigningKey() if err != nil { return err } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.ControllerConfig, bootstrapConstraints, args.ModelConstraints, result.Series, publicKey, ) if err != nil { return err } if err := instanceConfig.SetTools(selectedToolsList); err != nil { return errors.Trace(err) } // Make sure we have the most recent environ config as the specified // tools version has been updated there. cfg = environ.Config() if err := finalizeInstanceBootstrapConfig(ctx, instanceConfig, args, cfg, customImageMetadata); err != nil { return errors.Annotate(err, "finalizing bootstrap instance config") } if err := result.Finalize(ctx, instanceConfig, args.DialOpts); err != nil { return err } ctx.Infof("Bootstrap agent now started") return nil }