func (s *badBuildSuite) TestBuildToolsBadBuild(c *gc.C) { // Test that original BuildToolsTarball fails builtTools, err := sync.BuildToolsTarball(nil) c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `) c.Assert(builtTools, gc.IsNil) // Test that BuildToolsTarball func passes after BundleTools func is // mocked out s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) builtTools, err = sync.BuildToolsTarball(nil) c.Assert(builtTools.Version, gc.Equals, version.Current) c.Assert(err, gc.IsNil) }
func (s *uploadSuite) TestSyncTools(c *gc.C) { builtTools, err := sync.BuildToolsTarball(nil, "released") c.Assert(err, jc.ErrorIsNil) t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools) c.Assert(err, jc.ErrorIsNil) s.assertEqualsCurrentVersion(c, t.Version) c.Assert(t.URL, gc.Not(gc.Equals), "") }
func (s *uploadSuite) TestSyncTools(c *gc.C) { s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) builtTools, err := sync.BuildToolsTarball(nil) c.Assert(err, gc.IsNil) t, err := sync.SyncBuiltTools(s.env.Storage(), builtTools) c.Assert(err, gc.IsNil) c.Assert(t.Version, gc.Equals, version.Current) c.Assert(t.URL, gc.Not(gc.Equals), "") }
func (s *uploadSuite) TestSyncToolsFakeSeries(c *gc.C) { seriesToUpload := "precise" if seriesToUpload == version.Current.Series { seriesToUpload = "raring" } builtTools, err := sync.BuildToolsTarball(nil) c.Assert(err, gc.IsNil) t, err := sync.SyncBuiltTools(s.env.Storage(), builtTools, "quantal", seriesToUpload) c.Assert(err, gc.IsNil) s.assertUploadedTools(c, t, seriesToUpload) }
func (s *uploadSuite) TestSyncToolsFakeSeries(c *gc.C) { seriesToUpload := "precise" if seriesToUpload == series.HostSeries() { seriesToUpload = "raring" } builtTools, err := sync.BuildToolsTarball(nil, "testing") c.Assert(err, jc.ErrorIsNil) t, err := sync.SyncBuiltTools(s.targetStorage, "testing", builtTools, "quantal", seriesToUpload) c.Assert(err, jc.ErrorIsNil) s.assertUploadedTools(c, t, []string{seriesToUpload, "quantal", series.HostSeries()}, "testing") }
func (s *uploadSuite) TestSyncAndForceVersion(c *gc.C) { // This test actually tests three things: // the writing of the FORCE-VERSION file; // the reading of the FORCE-VERSION file by the version package; // and the reading of the version from jujud. vers := version.Current vers.Patch++ builtTools, err := sync.BuildToolsTarball(&vers.Number) c.Assert(err, gc.IsNil) t, err := sync.SyncBuiltTools(s.env.Storage(), builtTools) c.Assert(err, gc.IsNil) c.Assert(t.Version, gc.Equals, vers) }
func (s *uploadSuite) TestSyncAndForceVersion(c *gc.C) { // This test actually tests three things: // the writing of the FORCE-VERSION file; // the reading of the FORCE-VERSION file by the version package; // and the reading of the version from jujud. vers := version.Current vers.Patch++ builtTools, err := sync.BuildToolsTarball(&vers, "released") c.Assert(err, jc.ErrorIsNil) t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools) c.Assert(err, jc.ErrorIsNil) c.Assert(t.Version, gc.Equals, version.Binary{Number: vers, Arch: arch.HostArch(), Series: series.HostSeries()}) }
// uploadTools compiles jujud from $GOPATH and uploads it into the supplied // storage. If no version has been explicitly chosen, the version number // reported by the built tools will be based on the client version number. // In any case, the version number reported will have a build component higher // than that of any otherwise-matching available envtools. // uploadTools resets the chosen version and replaces the available tools // with the ones just uploaded. func (context *upgradeContext) uploadTools() (err error) { // TODO(fwereade): this is kinda crack: we should not assume that // jujuversion.Current matches whatever source happens to be built. The // ideal would be: // 1) compile jujud from $GOPATH into some build dir // 2) get actual version with `jujud version` // 3) check actual version for compatibility with CLI tools // 4) generate unique build version with reference to available tools // 5) force-version that unique version into the dir directly // 6) archive and upload the build dir // ...but there's no way we have time for that now. In the meantime, // considering the use cases, this should work well enough; but it // won't detect an incompatible major-version change, which is a shame. // // TODO(cherylj) If the determination of version changes, we will // need to also change the upgrade version checks in Run() that check // if a major upgrade is allowed. if context.chosen == version.Zero { context.chosen = context.client } context.chosen = uploadVersion(context.chosen, context.tools) builtTools, err := sync.BuildToolsTarball(&context.chosen, "upgrade") if err != nil { return errors.Trace(err) } defer os.RemoveAll(builtTools.Dir) var uploaded *coretools.Tools toolsPath := path.Join(builtTools.Dir, builtTools.StorageName) logger.Infof("uploading tools %v (%dkB) to Juju controller", builtTools.Version, (builtTools.Size+512)/1024) f, err := os.Open(toolsPath) if err != nil { return errors.Trace(err) } defer f.Close() os, err := series.GetOSFromSeries(builtTools.Version.Series) if err != nil { return errors.Trace(err) } additionalSeries := series.OSSupportedSeries(os) uploaded, err = context.apiClient.UploadTools(f, builtTools.Version, additionalSeries...) if err != nil { return errors.Trace(err) } context.tools = coretools.List{uploaded} return nil }
func (s *uploadSuite) TestMockBundleTools(c *gc.C) { var ( writer io.Writer forceVersion *version.Number n int p bytes.Buffer ) p.WriteString("Hello World") s.PatchValue(&envtools.BundleTools, func(writerArg io.Writer, forceVersionArg *version.Number) (vers version.Binary, sha256Hash string, err error) { writer = writerArg n, err = writer.Write(p.Bytes()) c.Assert(err, gc.IsNil) forceVersion = forceVersionArg return }) _, err := sync.BuildToolsTarball(&version.Current.Number) c.Assert(err, gc.IsNil) c.Assert(*forceVersion, gc.Equals, version.Current.Number) c.Assert(writer, gc.NotNil) c.Assert(n, gc.Equals, len(p.Bytes())) }
// uploadTools compiles jujud from $GOPATH and uploads it into the supplied // storage. If no version has been explicitly chosen, the version number // reported by the built tools will be based on the client version number. // In any case, the version number reported will have a build component higher // than that of any otherwise-matching available envtools. // uploadTools resets the chosen version and replaces the available tools // with the ones just uploaded. func (context *upgradeContext) uploadTools(series []string) (err error) { // TODO(fwereade): this is kinda crack: we should not assume that // version.Current matches whatever source happens to be built. The // ideal would be: // 1) compile jujud from $GOPATH into some build dir // 2) get actual version with `jujud version` // 3) check actual version for compatibility with CLI tools // 4) generate unique build version with reference to available tools // 5) force-version that unique version into the dir directly // 6) archive and upload the build dir // ...but there's no way we have time for that now. In the meantime, // considering the use cases, this should work well enough; but it // won't detect an incompatible major-version change, which is a shame. if context.chosen == version.Zero { context.chosen = context.client } context.chosen = uploadVersion(context.chosen, context.tools) builtTools, err := sync.BuildToolsTarball(&context.chosen) if err != nil { return err } defer os.RemoveAll(builtTools.Dir) var uploaded *coretools.Tools toolsPath := path.Join(builtTools.Dir, builtTools.StorageName) logger.Infof("uploading tools %v (%dkB) to Juju state server", builtTools.Version, (builtTools.Size+512)/1024) uploaded, err = context.apiClient.UploadTools(toolsPath, builtTools.Version, series...) if params.IsCodeNotImplemented(err) { uploaded, err = context.uploadTools1dot17(builtTools, series...) } if err != nil { return err } context.tools = coretools.List{uploaded} 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 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 }
// 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, ) 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 := 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 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 }
// 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") } // Write out the bootstrap-init file, and confirm storage is writeable. if err := environsVerifyStorage(environ.Storage()); err != nil { return err } ctx.Infof("Bootstrapping environment %q", cfg.Name()) logger.Debugf("environment %q supports service/machine networks: %v", cfg.Name(), environ.SupportNetworks()) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", disableNetworkManagement) availableTools, err := findAvailableTools(environ, args.Constraints.Arch, args.UploadTools) if errors.IsNotFound(err) { return errors.New(noToolsMessage) } else if err != nil { return 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 FinishMachineConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": version.Current.Number.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) if err != nil { return errors.Annotate(err, "cannot upload bootstrap tools") } 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") machineConfig, err := environs.NewBootstrapMachineConfig(args.Constraints, series) if err != nil { return err } machineConfig.Tools = selectedTools if err := finalizer(ctx, machineConfig); err != nil { return err } ctx.Infof("Bootstrap complete") return nil }