// startInstance is the internal version of StartInstance, used by Bootstrap // as well as via StartInstance itself. func (e *environ) startInstance(scfg *startInstanceParams) (environs.Instance, error) { if scfg.tools == nil { var err error flags := environs.HighestVersion | environs.CompatVersion scfg.tools, err = environs.FindTools(e, version.Current, flags) if err != nil { return nil, err } } log.Printf("environs/ec2: starting machine %s in %q running tools version %q from %q", scfg.machineId, e.name, scfg.tools.Binary, scfg.tools.URL) spec, err := findInstanceSpec(&instanceConstraint{ series: scfg.tools.Series, arch: scfg.tools.Arch, region: e.ecfg().region(), }) if err != nil { return nil, fmt.Errorf("cannot find image satisfying constraints: %v", err) } // TODO quick sanity check that we can access the tools URL? userData, err := e.userData(scfg) if err != nil { return nil, fmt.Errorf("cannot make user data: %v", err) } groups, err := e.setUpGroups(scfg.machineId) if err != nil { return nil, fmt.Errorf("cannot set up groups: %v", err) } var instances *ec2.RunInstancesResp for a := shortAttempt.Start(); a.Next(); { instances, err = e.ec2().RunInstances(&ec2.RunInstances{ ImageId: spec.imageId, MinCount: 1, MaxCount: 1, UserData: userData, InstanceType: "m1.small", SecurityGroups: groups, }) if err == nil || ec2ErrCode(err) != "InvalidGroup.NotFound" { break } } if err != nil { return nil, fmt.Errorf("cannot run instances: %v", err) } if len(instances.Instances) != 1 { return nil, fmt.Errorf("expected 1 started instance, got %d", len(instances.Instances)) } inst := &instance{e, &instances.Instances[0]} log.Printf("environs/ec2: started instance %q", inst.Id()) return inst, nil }
func (e *environ) Bootstrap(uploadTools bool, cert, key []byte) error { defer delay() if err := e.checkBroken("Bootstrap"); err != nil { return err } password := e.Config().AdminSecret() if password == "" { return fmt.Errorf("admin-secret is required for bootstrap") } if _, ok := e.Config().CACert(); !ok { return fmt.Errorf("no CA certificate in environment configuration") } var tools *state.Tools var err error if uploadTools { tools, err = environs.PutTools(e.Storage(), nil) if err != nil { return err } } else { flags := environs.HighestVersion | environs.CompatVersion tools, err = environs.FindTools(e, version.Current, flags) if err != nil { return err } } e.state.mu.Lock() defer e.state.mu.Unlock() e.state.ops <- OpBootstrap{Env: e.state.name} if e.state.bootstrapped { return fmt.Errorf("environment is already bootstrapped") } if e.ecfg().stateServer() { info := stateInfo() cfg, err := environs.BootstrapConfig(&providerInstance, e.ecfg().Config, tools) if err != nil { return fmt.Errorf("cannot make bootstrap config: %v", err) } st, err := state.Initialize(info, cfg) if err != nil { panic(err) } if err := st.SetAdminMongoPassword(trivial.PasswordHash(password)); err != nil { return err } if err := st.Close(); err != nil { panic(err) } } e.state.bootstrapped = true return nil }
// startInstance is the internal version of StartInstance, used by Bootstrap // as well as via StartInstance itself. func (e *environ) startInstance(scfg *startInstanceParams) (environs.Instance, error) { if scfg.tools == nil { var err error flags := environs.HighestVersion | environs.CompatVersion scfg.tools, err = environs.FindTools(e, version.Current, flags) if err != nil { return nil, err } } log.Printf("environs/openstack: starting machine %s in %q running tools version %q from %q", scfg.machineId, e.name, scfg.tools.Binary, scfg.tools.URL) // TODO(wallyworld) - implement spec lookup if strings.Contains(scfg.tools.Series, "unknown") || strings.Contains(scfg.tools.Series, "unknown") { return nil, fmt.Errorf("cannot find image for unknown series or architecture") } userData, err := e.userData(scfg) if err != nil { return nil, fmt.Errorf("cannot make user data: %v", err) } log.Debugf("environs/openstack: openstack user data: %q", userData) groups, err := e.setUpGroups(scfg.machineId) if err != nil { return nil, fmt.Errorf("cannot set up groups: %v", err) } var groupNames = make([]nova.SecurityGroupName, len(groups)) for i, g := range groups { groupNames[i] = nova.SecurityGroupName{g.Name} } var server *nova.Entity for a := shortAttempt.Start(); a.Next(); { server, err = e.nova().RunServer(nova.RunServerOpts{ Name: state.MachineEntityName(scfg.machineId), // TODO(wallyworld) - do not use hard coded image FlavorId: defaultFlavorId, ImageId: defaultImageId, UserData: userData, SecurityGroupNames: groupNames, }) if err == nil || !gooseerrors.IsNotFound(err) { break } } if err != nil { return nil, fmt.Errorf("cannot run instance: %v", err) } inst := &instance{e, server} log.Printf("environs/openstack: started instance %q", inst.Id()) return inst, nil }
func (*BootstrapSuite) TestBootstrapCommand(c *C) { defer makeFakeHome(c, "brokenenv").restore() err := ioutil.WriteFile(homePath(".juju", "environments.yaml"), []byte(envConfig), 0666) c.Assert(err, IsNil) // normal bootstrap opc, errc := runCommand(new(BootstrapCommand)) c.Check(<-errc, IsNil) c.Check((<-opc).(dummy.OpBootstrap).Env, Equals, "peckham") // Check that the CA certificate and key have been automatically generated // for the environment. _, err = os.Stat(homePath(".juju", "peckham-cert.pem")) c.Assert(err, IsNil) _, err = os.Stat(homePath(".juju", "peckham-private-key.pem")) c.Assert(err, IsNil) // bootstrap with tool uploading - checking that a file // is uploaded should be sufficient, as the detailed semantics // of UploadTools are tested in environs. opc, errc = runCommand(new(BootstrapCommand), "--upload-tools") c.Check(<-errc, IsNil) c.Check((<-opc).(dummy.OpPutFile).Env, Equals, "peckham") c.Check((<-opc).(dummy.OpBootstrap).Env, Equals, "peckham") envs, err := environs.ReadEnvirons("") c.Assert(err, IsNil) env, err := envs.Open("peckham") c.Assert(err, IsNil) tools, err := environs.FindTools(env, version.Current, environs.CompatVersion) c.Assert(err, IsNil) resp, err := http.Get(tools.URL) c.Assert(err, IsNil) defer resp.Body.Close() err = environs.UnpackTools(c.MkDir(), tools, resp.Body) c.Assert(err, IsNil) // bootstrap with broken environment opc, errc = runCommand(new(BootstrapCommand), "-e", "brokenenv") c.Check(<-errc, ErrorMatches, "dummy.Bootstrap is broken") c.Check(<-opc, IsNil) }
func (t *ToolsSuite) TestFindTools(c *C) { for i, tt := range findToolsTests { c.Logf("test %d", i) putNames(c, t.env, tt.contents, tt.publicContents) vers := version.Binary{ Number: tt.version, Series: version.Current.Series, Arch: version.Current.Arch, } tools, err := environs.FindTools(t.env, vers, tt.flags) if tt.err != "" { c.Assert(err, ErrorMatches, tt.err) } else { c.Assert(err, IsNil) assertURLContents(c, tools.URL, tt.expect) } t.env.Destroy(nil) } }
func (u *Upgrader) run() error { // Let the state know the version that is currently running. currentTools, err := environs.ReadTools(u.dataDir, version.Current) if err != nil { // Don't abort everything because we can't find the tools directory. // The problem should sort itself out as we will immediately // download some more tools and upgrade. log.Printf("cmd/jujud: upgrader cannot read current tools: %v", err) currentTools = &state.Tools{ Binary: version.Current, } } err = u.agentState.SetAgentTools(currentTools) if err != nil { return err } w := u.st.WatchEnvironConfig() defer watcher.Stop(w, &u.tomb) // Rather than using worker.WaitForEnviron, invalid environments are // managed explicitly so that all configuration changes are observed // by the loop below. var environ environs.Environ // TODO(rog) retry downloads when they fail. var ( download *downloader.Download downloadTools *state.Tools downloadDone <-chan downloader.Status ) // If we're killed early on (probably as a result of some other // task dying) we allow ourselves some time to try to connect to // the state and download a new version. We return to normal // undelayed behaviour when: // 1) We find there's no upgrade to do. // 2) A download fails. tomb := delayedTomb(&u.tomb, upgraderKillDelay) noDelay := func() { if tomb != &u.tomb { tomb.Kill(nil) tomb = &u.tomb } } for { // We wait for the tools to change while we're downloading // so that if something goes wrong (for instance a bad URL // hangs up) another change to the proposed tools can // potentially fix things. select { case cfg, ok := <-w.Changes(): if !ok { return watcher.MustErr(w) } var err error if environ == nil { environ, err = environs.New(cfg) if err != nil { log.Printf("cmd/jujud: upgrader loaded invalid initial environment configuration: %v", err) break } } else { err = environ.SetConfig(cfg) if err != nil { log.Printf("cmd/jujud: upgrader loaded invalid environment configuration: %v", err) // continue on, because the version number is still significant. } } vers := cfg.AgentVersion() if download != nil { // There's a download in progress, stop it if we need to. if vers == downloadTools.Number { // We are already downloading the requested tools. break } // Tools changed. We need to stop and restart. download.Stop() download, downloadTools, downloadDone = nil, nil, nil } // Ignore the proposed tools if we're already running the // proposed version. if vers == version.Current.Number { noDelay() break } binary := version.Current binary.Number = vers if tools, err := environs.ReadTools(u.dataDir, binary); err == nil { // The tools have already been downloaded, so use them. return u.upgradeReady(currentTools, tools) } flags := environs.CompatVersion if cfg.Development() { flags |= environs.DevVersion } tools, err := environs.FindTools(environ, binary, flags) if err != nil { log.Printf("cmd/jujud: upgrader error finding tools for %v: %v", binary, err) noDelay() // TODO(rog): poll until tools become available. break } if tools.Binary != binary { if tools.Number == version.Current.Number { // TODO(rog): poll until tools become available. log.Printf("cmd/jujud: upgrader: version %v requested but found only current version: %v", binary, tools.Number) noDelay() break } log.Printf("cmd/jujud: upgrader cannot find exact tools match for %s; using %s instead", binary, tools.Binary) } log.Printf("cmd/jujud: upgrader downloading %q", tools.URL) download = downloader.New(tools.URL, "") downloadTools = tools downloadDone = download.Done() case status := <-downloadDone: tools := downloadTools download, downloadTools, downloadDone = nil, nil, nil if status.Err != nil { log.Printf("cmd/jujud: upgrader download of %v failed: %v", tools.Binary, status.Err) noDelay() break } err := environs.UnpackTools(u.dataDir, tools, status.File) status.File.Close() if err := os.Remove(status.File.Name()); err != nil { log.Printf("cmd/jujud: upgrader cannot remove temporary download file: %v", err) } if err != nil { log.Printf("cmd/jujud: upgrader cannot unpack %v tools: %v", tools.Binary, err) noDelay() break } return u.upgradeReady(currentTools, tools) case <-tomb.Dying(): if download != nil { return fmt.Errorf("upgrader aborted download of %q", downloadTools.URL) } return nil } } panic("not reached") }
func (e *environ) Bootstrap(uploadTools bool, cert, key []byte) error { password := e.Config().AdminSecret() if password == "" { return fmt.Errorf("admin-secret is required for bootstrap") } log.Printf("environs/ec2: bootstrapping environment %q", e.name) // If the state file exists, it might actually have just been // removed by Destroy, and eventual consistency has not caught // up yet, so we retry to verify if that is happening. var err error for a := shortAttempt.Start(); a.Next(); { _, err = e.loadState() if err != nil { break } } if err == nil { return fmt.Errorf("environment is already bootstrapped") } if _, notFound := err.(*environs.NotFoundError); !notFound { return fmt.Errorf("cannot query old bootstrap state: %v", err) } var tools *state.Tools if uploadTools { tools, err = environs.PutTools(e.Storage(), nil) if err != nil { return fmt.Errorf("cannot upload tools: %v", err) } } else { flags := environs.HighestVersion | environs.CompatVersion v := version.Current v.Series = e.Config().DefaultSeries() tools, err = environs.FindTools(e, v, flags) if err != nil { return fmt.Errorf("cannot find tools: %v", err) } } config, err := environs.BootstrapConfig(providerInstance, e.Config(), tools) if err != nil { return fmt.Errorf("unable to determine inital configuration: %v", err) } caCert, hasCert := e.Config().CACert() if !hasCert { return fmt.Errorf("no CA certificate in environment configuration") } info := &state.Info{ Password: trivial.PasswordHash(password), CACert: caCert, } inst, err := e.startInstance(&startInstanceParams{ machineId: "0", info: info, tools: tools, stateServer: true, config: config, stateServerCert: cert, stateServerKey: key, }) if err != nil { return fmt.Errorf("cannot start bootstrap instance: %v", err) } err = e.saveState(&bootstrapState{ StateInstances: []state.InstanceId{inst.Id()}, }) if err != nil { // ignore error on StopInstance because the previous error is // more important. e.StopInstances([]environs.Instance{inst}) return fmt.Errorf("cannot save state: %v", err) } // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to use S3 to make sure that only one succeeds. // Perhaps consider using SimpleDB for state storage // which would enable that possibility. return nil }