func assertToolsList(c *gc.C, list coretools.List, expected []version.Binary) { urls := list.URLs() c.Check(urls, gc.HasLen, len(expected)) for _, vers := range expected { c.Assert(urls[vers], gc.Not(gc.Equals), "") } }
// findCompatibleTools finds tools in the list that have the same major, minor // and patch level as version.Current. // // Build number is not important to match; uploaded tools will have // incremented build number, and we want to match them. func findCompatibleTools(possibleTools coretools.List, version version.Number) (version.Number, coretools.List) { var compatibleTools coretools.List for _, tools := range possibleTools { if isCompatibleVersion(tools.Version.Number, version) { compatibleTools = append(compatibleTools, tools) } } return compatibleTools.Newest() }
// checkToolsSeries verifies that all the given possible tools are for the // given OS series. func checkToolsSeries(toolsList coretools.List, series string) error { toolsSeries := toolsList.AllSeries() if len(toolsSeries) != 1 { return fmt.Errorf("expected single series, got %v", toolsSeries) } if toolsSeries[0] != series { return fmt.Errorf("tools mismatch: expected series %v, got %v", series, toolsSeries[0]) } return nil }
// matchHostArchTools filters the given list of tools to the host architecture. func matchHostArchTools(allTools tools.List) (tools.List, error) { arch := arch.HostArch() archTools, err := allTools.Match(tools.Filter{Arch: arch}) if err == tools.ErrNoMatches { return nil, errors.Errorf( "need tools for arch %s, only found %s", arch, allTools.Arches(), ) } else if err != nil { return nil, errors.Trace(err) } return archTools, nil }
// SetBootstrapTools returns the newest tools from the given tools list, // and updates the agent-version configuration attribute. func SetBootstrapTools(environ environs.Environ, possibleTools coretools.List) (coretools.List, error) { if len(possibleTools) == 0 { return nil, fmt.Errorf("no bootstrap tools available") } var newVersion version.Number newVersion, toolsList := possibleTools.Newest() logger.Infof("newest version: %s", newVersion) cfg := environ.Config() if agentVersion, _ := cfg.AgentVersion(); agentVersion != newVersion { cfg, err := cfg.Apply(map[string]interface{}{ "agent-version": newVersion.String(), }) if err == nil { err = environ.SetConfig(cfg) } if err != nil { return nil, fmt.Errorf("failed to update environment configuration: %v", err) } } bootstrapVersion := newVersion // We should only ever bootstrap the exact same version as the client, // or we risk bootstrap incompatibility. We still set agent-version to // the newest version, so the agent will immediately upgrade itself. if !isCompatibleVersion(newVersion, version.Current.Number) { compatibleVersion, compatibleTools := findCompatibleTools(possibleTools, version.Current.Number) if len(compatibleTools) == 0 { logger.Warningf( "failed to find %s tools, will attempt to use %s", version.Current.Number, newVersion, ) } else { bootstrapVersion, toolsList = compatibleVersion, compatibleTools } } logger.Infof("picked bootstrap tools version: %s", bootstrapVersion) return toolsList, nil }
// getBootstrapToolsVersion returns the newest tools from the given tools list. func getBootstrapToolsVersion(possibleTools coretools.List) (coretools.List, error) { if len(possibleTools) == 0 { return nil, errors.New("no bootstrap tools available") } var newVersion version.Number newVersion, toolsList := possibleTools.Newest() logger.Infof("newest version: %s", newVersion) bootstrapVersion := newVersion // We should only ever bootstrap the exact same version as the client, // or we risk bootstrap incompatibility. if !isCompatibleVersion(newVersion, jujuversion.Current) { compatibleVersion, compatibleTools := findCompatibleTools(possibleTools, jujuversion.Current) if len(compatibleTools) == 0 { logger.Infof( "failed to find %s tools, will attempt to use %s", jujuversion.Current, newVersion, ) } else { bootstrapVersion, toolsList = compatibleVersion, compatibleTools } } logger.Infof("picked bootstrap tools version: %s", bootstrapVersion) return toolsList, nil }
func (s *ListSuite) TestURLs(c *gc.C) { empty := tools.List{} c.Check(empty.URLs(), gc.DeepEquals, map[version.Binary]string{}) full := tools.List{t100precise, t190quantal, t2001precise} c.Check(full.URLs(), gc.DeepEquals, map[version.Binary]string{ t100precise.Version: t100precise.URL, t190quantal.Version: t190quantal.URL, t2001precise.Version: t2001precise.URL, }) }
func (s *ListSuite) TestURLs(c *gc.C) { empty := tools.List{} c.Check(empty.URLs(), gc.DeepEquals, map[version.Binary][]string{}) alt := *t100quantal alt.URL = strings.Replace(alt.URL, "testing.invalid", "testing.invalid2", 1) full := tools.List{ t100precise, t190quantal, t100quantal, &alt, t2001precise, } c.Check(full.URLs(), gc.DeepEquals, map[version.Binary][]string{ t100precise.Version: []string{t100precise.URL}, t100quantal.Version: []string{t100quantal.URL, alt.URL}, t190quantal.Version: []string{t190quantal.URL}, t2001precise.Version: []string{t2001precise.URL}, }) }
// bootstrapImageMetadata returns the image metadata to use for bootstrapping // the given environment. If the environment provider does not make use of // simplestreams, no metadata will be returned. // // If a bootstrap image ID is specified, image metadata will be synthesised // using that image ID, and the architecture and series specified by the // initiator. In addition, the custom image metadata that is saved into the // state database will have the synthesised image metadata added to it. func bootstrapImageMetadata( environ environs.Environ, availableTools coretools.List, bootstrapImageId string, customImageMetadata *[]*imagemetadata.ImageMetadata, ) ([]*imagemetadata.ImageMetadata, error) { hasRegion, ok := environ.(simplestreams.HasRegion) if !ok { if bootstrapImageId != "" { // We only support specifying image IDs for providers // that use simplestreams for now. return nil, errors.NotSupportedf( "specifying bootstrap image for %q provider", environ.Config().Type(), ) } // No region, no metadata. return nil, nil } region, err := hasRegion.Region() if err != nil { return nil, errors.Trace(err) } if bootstrapImageId != "" { arches := availableTools.Arches() if len(arches) != 1 { return nil, errors.NotValidf("multiple architectures with bootstrap image") } allSeries := availableTools.AllSeries() if len(allSeries) != 1 { return nil, errors.NotValidf("multiple series with bootstrap image") } seriesVersion, err := series.SeriesVersion(allSeries[0]) if err != nil { return nil, errors.Trace(err) } // The returned metadata does not have information about the // storage or virtualisation type. Any provider that wants to // filter on those properties should allow for empty values. meta := &imagemetadata.ImageMetadata{ Id: bootstrapImageId, Arch: arches[0], Version: seriesVersion, RegionName: region.Region, Endpoint: region.Endpoint, Stream: environ.Config().ImageStream(), } *customImageMetadata = append(*customImageMetadata, meta) return []*imagemetadata.ImageMetadata{meta}, nil } // For providers that support making use of simplestreams // image metadata, search public image metadata. We need // to pass this onto Bootstrap for selecting images. sources, err := environs.ImageMetadataSources(environ) if err != nil { return nil, errors.Trace(err) } imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ CloudSpec: region, Series: availableTools.AllSeries(), Arches: availableTools.Arches(), Stream: environ.Config().ImageStream(), }) logger.Debugf("constraints for image metadata lookup %v", imageConstraint) // Get image metadata from all data sources. // Since order of data source matters, order of image metadata matters too. Append is important here. var publicImageMetadata []*imagemetadata.ImageMetadata for _, source := range sources { sourceMetadata, _, err := imagemetadata.Fetch([]simplestreams.DataSource{source}, imageConstraint) if err != nil { logger.Debugf("ignoring image metadata in %s: %v", source.Description(), err) // Just keep looking... continue } logger.Debugf("found %d image metadata in %s", len(sourceMetadata), source.Description()) publicImageMetadata = append(publicImageMetadata, sourceMetadata...) } logger.Debugf("found %d image metadata from all image data sources", len(publicImageMetadata)) if len(publicImageMetadata) == 0 { return nil, errors.New("no image metadata found") } return publicImageMetadata, nil }
// bootstrapImageMetadata returns the image metadata to use for bootstrapping // the given environment. If the environment provider does not make use of // simplestreams, no metadata will be returned. // // If a bootstrap image ID is specified, image metadat will be synthesised // using that image ID, and the architecture and series specified by the // initiator. In addition, the custom image metadat that is saved into the // state database will have the synthesised image metadata added to it. func bootstrapImageMetadata( environ environs.Environ, availableTools coretools.List, bootstrapImageId string, customImageMetadata *[]*imagemetadata.ImageMetadata, ) ([]*imagemetadata.ImageMetadata, error) { hasRegion, ok := environ.(simplestreams.HasRegion) if !ok { if bootstrapImageId != "" { // We only support specifying image IDs for providers // that use simplestreams for now. return nil, errors.NotSupportedf( "specifying bootstrap image for %q provider", environ.Config().Type(), ) } // No region, no metadata. return nil, nil } region, err := hasRegion.Region() if err != nil { return nil, errors.Trace(err) } if bootstrapImageId != "" { arches := availableTools.Arches() if len(arches) != 1 { return nil, errors.NotValidf("multiple architectures with bootstrap image") } allSeries := availableTools.AllSeries() if len(allSeries) != 1 { return nil, errors.NotValidf("multiple series with bootstrap image") } seriesVersion, err := series.SeriesVersion(allSeries[0]) if err != nil { return nil, errors.Trace(err) } // The returned metadata does not have information about the // storage or virtualisation type. Any provider that wants to // filter on those properties should allow for empty values. meta := &imagemetadata.ImageMetadata{ Id: bootstrapImageId, Arch: arches[0], Version: seriesVersion, RegionName: region.Region, Endpoint: region.Endpoint, Stream: environ.Config().ImageStream(), } *customImageMetadata = append(*customImageMetadata, meta) return []*imagemetadata.ImageMetadata{meta}, nil } // For providers that support making use of simplestreams // image metadata, search public image metadata. We need // to pass this onto Bootstrap for selecting images. sources, err := environs.ImageMetadataSources(environ) if err != nil { return nil, errors.Trace(err) } imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ CloudSpec: region, Series: availableTools.AllSeries(), Arches: availableTools.Arches(), Stream: environ.Config().ImageStream(), }) publicImageMetadata, _, err := imagemetadata.Fetch(sources, imageConstraint) if err != nil { return nil, errors.Annotate(err, "searching image metadata") } return publicImageMetadata, 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 }