// SetConstraints replaces the current service constraints. func (s *Service) SetConstraints(cons constraints.Value) (err error) { unsupported, err := s.st.validateConstraints(cons) if len(unsupported) > 0 { logger.Warningf( "setting constraints on service %q: unsupported constraints: %v", s.Name(), strings.Join(unsupported, ",")) } else if err != nil { return err } if s.doc.Subordinate { return ErrSubordinateConstraints } defer errors.Maskf(&err, "cannot set constraints") if s.doc.Life != Alive { return errNotAlive } ops := []txn.Op{ { C: s.st.services.Name, Id: s.doc.Name, Assert: isAliveDoc, }, setConstraintsOp(s.st, s.globalKey(), cons), } return onAbort(s.st.runTransaction(ops), errNotAlive) }
// AssignUnit places the unit on a machine. Depending on the policy, and the // state of the environment, this may lead to new instances being launched // within the environment. func (st *State) AssignUnit(u *Unit, policy AssignmentPolicy) (err error) { if !u.IsPrincipal() { return fmt.Errorf("subordinate unit %q cannot be assigned directly to a machine", u) } defer errors.Maskf(&err, "cannot assign unit %q to machine", u) var m *Machine switch policy { case AssignLocal: m, err = st.Machine("0") if err != nil { return err } return u.AssignToMachine(m) case AssignClean: if _, err = u.AssignToCleanMachine(); err != noCleanMachines { return err } return u.AssignToNewMachineOrContainer() case AssignCleanEmpty: if _, err = u.AssignToCleanEmptyMachine(); err != noCleanMachines { return err } return u.AssignToNewMachineOrContainer() case AssignNew: return u.AssignToNewMachine() } return fmt.Errorf("unknown unit assignment policy: %q", policy) }
// Write atomically writes to disk the relation state change in hi. // It must be called after the respective hook was executed successfully. // Write doesn't validate hi but guarantees that successive writes of // the same hi are idempotent. func (d *StateDir) Write(hi hook.Info) (err error) { defer errors.Maskf(&err, "failed to write %q hook info for %q on state directory", hi.Kind, hi.RemoteUnit) if hi.Kind == hooks.RelationBroken { return d.Remove() } name := strings.Replace(hi.RemoteUnit, "/", "-", 1) path := filepath.Join(d.path, name) if hi.Kind == hooks.RelationDeparted { if err = os.Remove(path); err != nil && !os.IsNotExist(err) { return err } // If atomic delete succeeded, update own state. delete(d.state.Members, hi.RemoteUnit) return nil } di := diskInfo{&hi.ChangeVersion, hi.Kind == hooks.RelationJoined} if err := utils.WriteYaml(path, &di); err != nil { return err } // If write was successful, update own state. d.state.Members[hi.RemoteUnit] = hi.ChangeVersion if hi.Kind == hooks.RelationJoined { d.state.ChangedPending = hi.RemoteUnit } else { d.state.ChangedPending = "" } return nil }
// Destroy ensures that the relation will be removed at some point; if no units // are currently in scope, it will be removed immediately. func (r *Relation) Destroy() (err error) { defer errors.Maskf(&err, "cannot destroy relation %q", r) if len(r.doc.Endpoints) == 1 && r.doc.Endpoints[0].Role == charm.RolePeer { return fmt.Errorf("is a peer relation") } defer func() { if err == nil { // This is a white lie; the document might actually be removed. r.doc.Life = Dying } }() rel := &Relation{r.st, r.doc} // In this context, aborted transactions indicate that the number of units // in scope have changed between 0 and not-0. The chances of 5 successive // attempts each hitting this change -- which is itself an unlikely one -- // are considered to be extremely small. for attempt := 0; attempt < 5; attempt++ { ops, _, err := rel.destroyOps("") if err == errAlreadyDying { return nil } else if err != nil { return err } if err := rel.st.runTransaction(ops); err != txn.ErrAborted { return err } if err := rel.Refresh(); errors.IsNotFound(err) { return nil } else if err != nil { return err } } return ErrExcessiveContention }
// ReadAllStateDirs loads and returns every StateDir persisted directly inside // the supplied dirPath. If dirPath does not exist, no error is returned. func ReadAllStateDirs(dirPath string) (dirs map[int]*StateDir, err error) { defer errors.Maskf(&err, "cannot load relations state from %q", dirPath) if _, err := os.Stat(dirPath); os.IsNotExist(err) { return nil, nil } else if err != nil { return nil, err } fis, err := ioutil.ReadDir(dirPath) if err != nil { return nil, err } dirs = map[int]*StateDir{} for _, fi := range fis { // Entries with integer names must be directories containing StateDir // data; all other names will be ignored. relationId, err := strconv.Atoi(fi.Name()) if err != nil { // This doesn't look like a relation. continue } dir, err := ReadStateDir(dirPath, relationId) if err != nil { return nil, err } dirs[relationId] = dir } return dirs, nil }
// SetMinUnits changes the number of minimum units required by the service. func (s *Service) SetMinUnits(minUnits int) (err error) { defer coreerrors.Maskf(&err, "cannot set minimum units for service %q", s) defer func() { if err == nil { s.doc.MinUnits = minUnits } }() if minUnits < 0 { return errors.New("cannot set a negative minimum number of units") } service := &Service{st: s.st, doc: s.doc} // Removing the document never fails. Racing clients trying to create the // document generate one failure, but the second attempt should succeed. // If one client tries to update the document, and a racing client removes // it, the former should be able to re-create the document in the second // attempt. If the referred-to service advanced its life cycle to a not // alive state, an error is returned after the first failing attempt. for i := 0; i < 2; i++ { if service.doc.Life != Alive { return errors.New("service is no longer alive") } if minUnits == service.doc.MinUnits { return nil } ops := setMinUnitsOps(service, minUnits) if err := s.st.runTransaction(ops); err != txn.ErrAborted { return err } if err := service.Refresh(); err != nil { return err } } return ErrExcessiveContention }
// Validate returns an error if the supplied hook.Info does not represent // a valid change to the relation state. Hooks must always be validated // against the current state before they are run, to ensure that the system // meets its guarantees about hook execution order. func (s *State) Validate(hi hook.Info) (err error) { defer errors.Maskf(&err, "inappropriate %q for %q", hi.Kind, hi.RemoteUnit) if hi.RelationId != s.RelationId { return fmt.Errorf("expected relation %d, got relation %d", s.RelationId, hi.RelationId) } if s.Members == nil { return fmt.Errorf(`relation is broken and cannot be changed further`) } unit, kind := hi.RemoteUnit, hi.Kind if kind == hooks.RelationBroken { if len(s.Members) == 0 { return nil } return fmt.Errorf(`cannot run "relation-broken" while units still present`) } if s.ChangedPending != "" { if unit != s.ChangedPending || kind != hooks.RelationChanged { return fmt.Errorf(`expected "relation-changed" for %q`, s.ChangedPending) } } else if _, joined := s.Members[unit]; joined && kind == hooks.RelationJoined { return fmt.Errorf("unit already joined") } else if !joined && kind != hooks.RelationJoined { return fmt.Errorf("unit has not joined") } return nil }
// validate returns an error if the state violates expectations. func (st State) validate() (err error) { defer coreerrors.Maskf(&err, "invalid uniter state") hasHook := st.Hook != nil hasCharm := st.CharmURL != nil switch st.Op { case Install: if hasHook { return fmt.Errorf("unexpected hook info") } fallthrough case Upgrade: if !hasCharm { return fmt.Errorf("missing charm URL") } case Continue, RunHook: if !hasHook { return fmt.Errorf("missing hook info") } else if hasCharm { return fmt.Errorf("unexpected charm URL") } default: return fmt.Errorf("unknown operation %q", st.Op) } switch st.OpStep { case Queued, Pending, Done: default: return fmt.Errorf("unknown operation step %q", st.OpStep) } if hasHook { return st.Hook.Validate() } return nil }
// Destroy ensures that the service and all its relations will be removed at // some point; if the service has no units, and no relation involving the // service has any units in scope, they are all removed immediately. func (s *Service) Destroy() (err error) { defer errors.Maskf(&err, "cannot destroy service %q", s) defer func() { if err == nil { // This is a white lie; the document might actually be removed. s.doc.Life = Dying } }() svc := &Service{st: s.st, doc: s.doc} for i := 0; i < 5; i++ { switch ops, err := svc.destroyOps(); err { case errRefresh: case errAlreadyDying: return nil case nil: if err := svc.st.runTransaction(ops); err != txn.ErrAborted { return err } default: return err } if err := svc.Refresh(); errors.IsNotFound(err) { return nil } else if err != nil { return err } } return ErrExcessiveContention }
// SetResolved marks the unit as having had any previous state transition // problems resolved, and informs the unit that it may attempt to // reestablish normal workflow. The resolved mode parameter informs // whether to attempt to reexecute previous failed hooks or to continue // as if they had succeeded before. func (u *Unit) SetResolved(mode ResolvedMode) (err error) { defer errors.Maskf(&err, "cannot set resolved mode for unit %q", u) switch mode { case ResolvedRetryHooks, ResolvedNoHooks: default: return fmt.Errorf("invalid error resolution mode: %q", mode) } // TODO(fwereade): assert unit has error status. resolvedNotSet := bson.D{{"resolved", ResolvedNone}} ops := []txn.Op{{ C: u.st.units.Name, Id: u.doc.Name, Assert: append(notDeadDoc, resolvedNotSet...), Update: bson.D{{"$set", bson.D{{"resolved", mode}}}}, }} if err := u.st.runTransaction(ops); err == nil { u.doc.Resolved = mode return nil } else if err != txn.ErrAborted { return err } if ok, err := isNotDead(u.st.units, u.doc.Name); err != nil { return err } else if !ok { return errDead } // For now, the only remaining assert is that resolved was unset. return fmt.Errorf("already resolved") }
// FinishMachineConfig sets fields on a MachineConfig that can be determined by // inspecting a plain config.Config and the machine constraints at the last // moment before bootstrapping. It assumes that the supplied Config comes from // an environment that has passed through all the validation checks in the // Bootstrap func, and that has set an agent-version (via finding the tools to, // use for bootstrap, or otherwise). // TODO(fwereade) This function is not meant to be "good" in any serious way: // it is better that this functionality be collected in one place here than // that it be spread out across 3 or 4 providers, but this is its only // redeeming feature. func FinishMachineConfig(mcfg *cloudinit.MachineConfig, cfg *config.Config, cons constraints.Value) (err error) { defer errors.Maskf(&err, "cannot complete machine configuration") if err := PopulateMachineConfig( mcfg, cfg.Type(), cfg.AuthorizedKeys(), cfg.SSLHostnameVerification(), cfg.ProxySettings(), cfg.AptProxySettings(), ); err != nil { return err } // The following settings are only appropriate at bootstrap time. At the // moment, the only state server is the bootstrap node, but this // will probably change. if !mcfg.Bootstrap { return nil } if mcfg.APIInfo != nil || mcfg.StateInfo != nil { return fmt.Errorf("machine configuration already has api/state info") } caCert, hasCACert := cfg.CACert() if !hasCACert { return fmt.Errorf("environment configuration has no ca-cert") } password := cfg.AdminSecret() if password == "" { return fmt.Errorf("environment configuration has no admin-secret") } passwordHash := utils.UserPasswordHash(password, utils.CompatSalt) mcfg.APIInfo = &api.Info{Password: passwordHash, CACert: caCert} mcfg.StateInfo = &state.Info{Password: passwordHash, CACert: caCert} // These really are directly relevant to running a state server. cert, key, err := cfg.GenerateStateServerCertAndKey() if err != nil { return errgo.Annotate(err, "cannot generate state server certificate") } srvInfo := params.StateServingInfo{ StatePort: cfg.StatePort(), APIPort: cfg.APIPort(), Cert: string(cert), PrivateKey: string(key), SystemIdentity: mcfg.SystemPrivateSSHKey, } mcfg.StateServingInfo = &srvInfo mcfg.Constraints = cons if mcfg.Config, err = BootstrapConfig(cfg); err != nil { return err } return nil }
// SetProvisioned sets the provider specific machine id, nonce and also metadata for // this machine. Once set, the instance id cannot be changed. // // When provisioning an instance, a nonce should be created and passed // when starting it, before adding the machine to the state. This means // that if the provisioner crashes (or its connection to the state is // lost) after starting the instance, we can be sure that only a single // instance will be able to act for that machine. func (m *Machine) SetProvisioned(id instance.Id, nonce string, characteristics *instance.HardwareCharacteristics) (err error) { defer errors.Maskf(&err, "cannot set instance data for machine %q", m) if id == "" || nonce == "" { return fmt.Errorf("instance id and nonce cannot be empty") } if characteristics == nil { characteristics = &instance.HardwareCharacteristics{} } instData := &instanceData{ Id: m.doc.Id, InstanceId: id, Arch: characteristics.Arch, Mem: characteristics.Mem, RootDisk: characteristics.RootDisk, CpuCores: characteristics.CpuCores, CpuPower: characteristics.CpuPower, Tags: characteristics.Tags, } // SCHEMACHANGE // TODO(wallyworld) - do not check instanceId on machineDoc after schema is upgraded notSetYet := bson.D{{"instanceid", ""}, {"nonce", ""}} ops := []txn.Op{ { C: m.st.machines.Name, Id: m.doc.Id, Assert: append(isAliveDoc, notSetYet...), Update: bson.D{{"$set", bson.D{{"instanceid", id}, {"nonce", nonce}}}}, }, { C: m.st.instanceData.Name, Id: m.doc.Id, Assert: txn.DocMissing, Insert: instData, }, } if err = m.st.runTransaction(ops); err == nil { m.doc.Nonce = nonce // SCHEMACHANGE // TODO(wallyworld) - remove this backward compatibility code when schema upgrades are possible // (InstanceId is stored on the instanceData document but we duplicate the value on the machineDoc. m.doc.InstanceId = id return nil } else if err != txn.ErrAborted { return err } else if alive, err := isAlive(m.st.machines, m.doc.Id); err != nil { return err } else if !alive { return errNotAlive } return fmt.Errorf("already set") }
func serviceRelations(st *State, name string) (relations []*Relation, err error) { defer errors.Maskf(&err, "can't get relations for service %q", name) docs := []relationDoc{} err = st.relations.Find(bson.D{{"endpoints.servicename", name}}).All(&docs) if err != nil { return nil, err } for _, v := range docs { relations = append(relations, newRelation(st, &v)) } return relations, nil }
// EnsureMinUnits adds new units if the service's MinUnits value is greater // than the number of alive units. func (s *Service) EnsureMinUnits() (err error) { defer coreerrors.Maskf(&err, "cannot ensure minimum units for service %q", s) service := &Service{st: s.st, doc: s.doc} for { // Ensure the service is alive. if service.doc.Life != Alive { return errors.New("service is not alive") } // Exit without errors if the MinUnits for the service is not set. if service.doc.MinUnits == 0 { return nil } // Retrieve the number of alive units for the service. aliveUnits, err := aliveUnitsCount(service) if err != nil { return err } // Calculate the number of required units to be added. missing := service.doc.MinUnits - aliveUnits if missing <= 0 { return nil } name, ops, err := ensureMinUnitsOps(service) if err != nil { return err } // Add missing unit. switch err := s.st.runTransaction(ops); err { case nil: // Assign the new unit. unit, err := service.Unit(name) if err != nil { return err } if err := service.st.AssignUnit(unit, AssignNew); err != nil { return err } // No need to proceed and refresh the service if this was the // last/only missing unit. if missing == 1 { return nil } case txn.ErrAborted: // Refresh the service and restart the loop. default: return err } if err := service.Refresh(); err != nil { return err } } }
// SetAnnotations adds key/value pairs to annotations in MongoDB. func (a *annotator) SetAnnotations(pairs map[string]string) (err error) { defer errors.Maskf(&err, "cannot update annotations on %s", a.tag) if len(pairs) == 0 { return nil } // Collect in separate maps pairs to be inserted/updated or removed. toRemove := make(map[string]bool) toInsert := make(map[string]string) toUpdate := make(map[string]string) for key, value := range pairs { if strings.Contains(key, ".") { return fmt.Errorf("invalid key %q", key) } if value == "" { toRemove["annotations."+key] = true } else { toInsert[key] = value toUpdate["annotations."+key] = value } } // Two attempts should be enough to update annotations even with racing // clients - if the document does not already exist, one of the clients // will create it and the others will fail, then all the rest of the // clients should succeed on their second attempt. If the referred-to // entity has disappeared, and removed its annotations in the meantime, // we consider that worthy of an error (will be fixed when new entities // can never share names with old ones). for i := 0; i < 2; i++ { var ops []txn.Op if count, err := a.st.annotations.FindId(a.globalKey).Count(); err != nil { return err } else if count == 0 { // Check that the annotator entity was not previously destroyed. if i != 0 { return fmt.Errorf("%s no longer exists", a.tag) } ops, err = a.insertOps(toInsert) if err != nil { return err } } else { ops = a.updateOps(toUpdate, toRemove) } if err := a.st.runTransaction(ops); err == nil { return nil } else if err != txn.ErrAborted { return err } } return ErrExcessiveContention }
// download fetches the supplied charm and checks that it has the correct sha256 // hash, then copies it into the directory. If a value is received on abort, the // download will be stopped. func (d *BundlesDir) download(info BundleInfo, abort <-chan struct{}) (err error) { archiveURL, disableSSLHostnameVerification, err := info.ArchiveURL() if err != nil { return err } defer errors.Maskf(&err, "failed to download charm %q from %q", info.URL(), archiveURL) dir := d.downloadsPath() if err := os.MkdirAll(dir, 0755); err != nil { return err } aurl := archiveURL.String() logger.Infof("downloading %s from %s", info.URL(), aurl) if disableSSLHostnameVerification { logger.Infof("SSL hostname verification disabled") } dl := downloader.New(aurl, dir, disableSSLHostnameVerification) defer dl.Stop() for { select { case <-abort: logger.Infof("download aborted") return fmt.Errorf("aborted") case st := <-dl.Done(): if st.Err != nil { return st.Err } logger.Infof("download complete") defer st.File.Close() actualSha256, _, err := utils.ReadSHA256(st.File) if err != nil { return err } archiveSha256, err := info.ArchiveSha256() if err != nil { return err } if actualSha256 != archiveSha256 { return fmt.Errorf( "expected sha256 %q, got %q", archiveSha256, actualSha256, ) } logger.Infof("download verified") if err := os.MkdirAll(d.path, 0755); err != nil { return err } return os.Rename(st.File.Name(), d.bundlePath(info)) } } }
// ReadSettings returns a map holding the settings of the unit with the // supplied name within this relation. An error will be returned if the // relation no longer exists, or if the unit's service is not part of the // relation, or the settings are invalid; but mere non-existence of the // unit is not grounds for an error, because the unit settings are // guaranteed to persist for the lifetime of the relation, regardless // of the lifetime of the unit. func (ru *RelationUnit) ReadSettings(uname string) (m map[string]interface{}, err error) { defer errors.Maskf(&err, "cannot read settings for unit %q in relation %q", uname, ru.relation) if !names.IsUnit(uname) { return nil, fmt.Errorf("%q is not a valid unit name", uname) } key, err := ru.key(uname) if err != nil { return nil, err } node, err := readSettings(ru.st, key) if err != nil { return nil, err } return node.Map(), nil }
// Remove removes the unit from state, and may remove its service as well, if // the service is Dying and no other references to it exist. It will fail if // the unit is not Dead. func (u *Unit) Remove() (err error) { defer errors.Maskf(&err, "cannot remove unit %q", u) if u.doc.Life != Dead { return stderrors.New("unit is not dead") } // Now the unit is Dead, we can be sure that it's impossible for it to // enter relation scopes (once it's Dying, we can be sure of this; but // EnsureDead does not require that it already be Dying, so this is the // only point at which we can safely backstop lp:1233457 and mitigate // the impact of unit agent bugs that leave relation scopes occupied). relations, err := serviceRelations(u.st, u.doc.Service) if err != nil { return err } for _, rel := range relations { ru, err := rel.Unit(u) if err != nil { return err } if err := ru.LeaveScope(); err != nil { return err } } // Now we're sure we haven't left any scopes occupied by this unit, we // can safely remove the document. unit := &Unit{st: u.st, doc: u.doc} for i := 0; i < 5; i++ { switch ops, err := unit.removeOps(isDeadDoc); err { case errRefresh: case errAlreadyRemoved: return nil case nil: if err := u.st.runTransaction(ops); err != txn.ErrAborted { return err } default: return err } if err := unit.Refresh(); errors.IsNotFound(err) { return nil } else if err != nil { return err } } return ErrExcessiveContention }
// ReadStateDir loads a StateDir from the subdirectory of dirPath named // for the supplied RelationId. If the directory does not exist, no error // is returned, func ReadStateDir(dirPath string, relationId int) (d *StateDir, err error) { d = &StateDir{ filepath.Join(dirPath, strconv.Itoa(relationId)), State{relationId, map[string]int64{}, ""}, } defer errors.Maskf(&err, "cannot load relation state from %q", d.path) if _, err := os.Stat(d.path); os.IsNotExist(err) { return d, nil } else if err != nil { return nil, err } fis, err := ioutil.ReadDir(d.path) if err != nil { return nil, err } for _, fi := range fis { // Entries with names ending in "-" followed by an integer must be // files containing valid unit data; all other names are ignored. name := fi.Name() i := strings.LastIndex(name, "-") if i == -1 { continue } svcName := name[:i] unitId := name[i+1:] if _, err := strconv.Atoi(unitId); err != nil { continue } unitName := svcName + "/" + unitId var info diskInfo if err = utils.ReadYaml(filepath.Join(d.path, name), &info); err != nil { return nil, fmt.Errorf("invalid unit file %q: %v", name, err) } if info.ChangeVersion == nil { return nil, fmt.Errorf(`invalid unit file %q: "changed-version" not set`, name) } d.state.Members[unitName] = *info.ChangeVersion if info.ChangedPending { if d.state.ChangedPending != "" { return nil, fmt.Errorf("%q and %q both have pending changed hooks", d.state.ChangedPending, unitName) } d.state.ChangedPending = unitName } } return d, nil }
// newTempCertFile stores the given x509 certificate in a temporary file, // which only the current user will be allowed to access. // You *must* clean up the file after use, by calling its Delete method. func newTempCertFile(data []byte) (certFile *tempCertFile, err error) { // Add context to any error we may return. defer errors.Maskf(&err, "failed while writing temporary certificate file") // Access permissions for these temporary files: const ( // Owner can read/write temporary files. Not backed up. fileMode = 0600 | os.ModeTemporary | os.ModeExclusive // Temporary dirs are like files, but owner also has "x" // permission. dirMode = fileMode | 0100 ) certFile = &tempCertFile{} // We'll randomize the file's name, so that even someone with access // to the temporary directory (perhaps a group member sneaking in // just before we close access to the directory) won't be able to // guess its name and inject their own file. certFile.filename = fmt.Sprintf("x509-%d.cert", rand.Int31()) // To guarantee that nobody else will be able to access the file, even // by predicting or guessing its name, we create the file in its own // private directory. certFile.tempDir, err = ioutil.TempDir("", "juju-azure") if err != nil { return nil, err } err = os.Chmod(certFile.tempDir, dirMode) if err != nil { return nil, err } // Now, at last, write the file. WriteFile could have done most of // the work on its own, but it doesn't guarantee that nobody creates // a file of the same name first. When that happens, you get a file // but not with the requested permissions. err = ioutil.WriteFile(certFile.Path(), data, fileMode) if err != nil { os.RemoveAll(certFile.tempDir) return nil, err } return certFile, nil }
// SetAgentVersion sets the version of juju that the agent is // currently running. func (u *Unit) SetAgentVersion(v version.Binary) (err error) { defer errors.Maskf(&err, "cannot set agent version for unit %q", u) if err = checkVersionValidity(v); err != nil { return err } tools := &tools.Tools{Version: v} ops := []txn.Op{{ C: u.st.units.Name, Id: u.doc.Name, Assert: notDeadDoc, Update: bson.D{{"$set", bson.D{{"tools", tools}}}}, }} if err := u.st.runTransaction(ops); err != nil { return onAbort(err, errDead) } u.doc.Tools = tools return nil }
// AddUnit adds a new principal unit to the service. func (s *Service) AddUnit() (unit *Unit, err error) { defer errors.Maskf(&err, "cannot add unit to service %q", s) name, ops, err := s.addUnitOps("", nil) if err != nil { return nil, err } if err := s.st.runTransaction(ops); err == txn.ErrAborted { if alive, err := isAlive(s.st.services, s.doc.Name); err != nil { return nil, err } else if !alive { return nil, fmt.Errorf("service is not alive") } return nil, fmt.Errorf("inconsistent state") } else if err != nil { return nil, err } return s.Unit(name) }
// SetAgentVersion sets the version of juju that the agent is // currently running. func (m *Machine) SetAgentVersion(v version.Binary) (err error) { defer errors.Maskf(&err, "cannot set agent version for machine %v", m) if err = checkVersionValidity(v); err != nil { return err } tools := &tools.Tools{Version: v} ops := []txn.Op{{ C: m.st.machines.Name, Id: m.doc.Id, Assert: notDeadDoc, Update: bson.D{{"$set", bson.D{{"tools", tools}}}}, }} if err := m.st.runTransaction(ops); err != nil { return onAbort(err, errDead) } m.doc.Tools = tools return nil }
// WaitAgentAlive blocks until the respective agent is alive. func (m *Machine) WaitAgentAlive(timeout time.Duration) (err error) { defer errors.Maskf(&err, "waiting for agent of machine %v", m) ch := make(chan presence.Change) m.st.pwatcher.Watch(m.globalKey(), ch) defer m.st.pwatcher.Unwatch(m.globalKey(), ch) for i := 0; i < 2; i++ { select { case change := <-ch: if change.Alive { return nil } case <-time.After(timeout): return fmt.Errorf("still not alive after timeout") case <-m.st.pwatcher.Dead(): return m.st.pwatcher.Err() } } panic(fmt.Sprintf("presence reported dead status twice in a row for machine %v", m)) }
// Units returns all the units that have been assigned to the machine. func (m *Machine) Units() (units []*Unit, err error) { defer errors.Maskf(&err, "cannot get units assigned to machine %v", m) pudocs := []unitDoc{} err = m.st.units.Find(bson.D{{"machineid", m.doc.Id}}).All(&pudocs) if err != nil { return nil, err } for _, pudoc := range pudocs { units = append(units, newUnit(m.st, &pudoc)) docs := []unitDoc{} err = m.st.units.Find(bson.D{{"principal", pudoc.Name}}).All(&docs) if err != nil { return nil, err } for _, doc := range docs { units = append(units, newUnit(m.st, &doc)) } } return units, nil }
// SetConstraints sets the exact constraints to apply when provisioning an // instance for the machine. It will fail if the machine is Dead, or if it // is already provisioned. func (m *Machine) SetConstraints(cons constraints.Value) (err error) { defer errors.Maskf(&err, "cannot set constraints") unsupported, err := m.st.validateConstraints(cons) if len(unsupported) > 0 { logger.Warningf( "setting constraints on machine %q: unsupported constraints: %v", m.Id(), strings.Join(unsupported, ",")) } else if err != nil { return err } notSetYet := bson.D{{"nonce", ""}} ops := []txn.Op{ { C: m.st.machines.Name, Id: m.doc.Id, Assert: append(isAliveDoc, notSetYet...), }, setConstraintsOp(m.st, m.globalKey(), cons), } // 3 attempts is enough to push the ErrExcessiveContention case out of the // realm of plausibility: it implies local state indicating unprovisioned, // and remote state indicating provisioned (reasonable); but which changes // back to unprovisioned and then to provisioned again with *very* specific // timing in the course of this loop. for i := 0; i < 3; i++ { if m.doc.Life != Alive { return errNotAlive } if _, err := m.InstanceId(); err == nil { return fmt.Errorf("machine is already provisioned") } else if !IsNotProvisionedError(err) { return err } if err := m.st.runTransaction(ops); err != txn.ErrAborted { return err } if m, err = m.st.Machine(m.doc.Id); err != nil { return err } } return ErrExcessiveContention }
// ClosePort sets the policy of the port with protocol and number to be closed. func (u *Unit) ClosePort(protocol string, number int) (err error) { port := instance.Port{Protocol: protocol, Number: number} defer errors.Maskf(&err, "cannot close port %v for unit %q", port, u) ops := []txn.Op{{ C: u.st.units.Name, Id: u.doc.Name, Assert: notDeadDoc, Update: bson.D{{"$pull", bson.D{{"ports", port}}}}, }} err = u.st.runTransaction(ops) if err != nil { return onAbort(err, errDead) } newPorts := make([]instance.Port, 0, len(u.doc.Ports)) for _, p := range u.doc.Ports { if p != port { newPorts = append(newPorts, p) } } u.doc.Ports = newPorts return nil }
// SetInstanceStatus sets the provider specific instance status for a machine. func (m *Machine) SetInstanceStatus(status string) (err error) { defer errors.Maskf(&err, "cannot set instance status for machine %q", m) // SCHEMACHANGE - we can't do this yet until the schema is updated // so just do a txn.DocExists for now. // provisioned := bson.D{{"instanceid", bson.D{{"$ne", ""}}}} ops := []txn.Op{ { C: m.st.instanceData.Name, Id: m.doc.Id, Assert: txn.DocExists, Update: bson.D{{"$set", bson.D{{"status", status}}}}, }, } if err = m.st.runTransaction(ops); err == nil { return nil } else if err != txn.ErrAborted { return err } return NotProvisionedError(m.Id()) }
// AddMachines adds new machines configured according to the // given templates. func (st *State) AddMachines(templates ...MachineTemplate) (_ []*Machine, err error) { defer errors.Maskf(&err, "cannot add a new machine") var ms []*Machine env, err := st.Environment() if err != nil { return nil, err } else if env.Life() != Alive { return nil, fmt.Errorf("environment is no longer alive") } var ops []txn.Op var mdocs []*machineDoc for _, template := range templates { // Adding a machine without any principals is // only permitted if unit placement is supported. if len(template.principals) == 0 && template.InstanceId == "" { if err := st.supportsUnitPlacement(); err != nil { return nil, err } } mdoc, addOps, err := st.addMachineOps(template) if err != nil { return nil, err } mdocs = append(mdocs, mdoc) ms = append(ms, newMachine(st, mdoc)) ops = append(ops, addOps...) } ssOps, err := st.maintainStateServersOps(mdocs, nil) if err != nil { return nil, err } ops = append(ops, ssOps...) ops = append(ops, env.assertAliveOp()) if err := st.runTransaction(ops); err != nil { return nil, onAbort(err, fmt.Errorf("environment is no longer alive")) } return ms, nil }
// Remove removes the machine from state. It will fail if the machine // is not Dead. func (m *Machine) Remove() (err error) { defer errors.Maskf(&err, "cannot remove machine %s", m.doc.Id) if m.doc.Life != Dead { return fmt.Errorf("machine is not dead") } ops := []txn.Op{ { C: m.st.machines.Name, Id: m.doc.Id, Assert: txn.DocExists, Remove: true, }, { C: m.st.machines.Name, Id: m.doc.Id, Assert: isDeadDoc, }, { C: m.st.instanceData.Name, Id: m.doc.Id, Remove: true, }, removeStatusOp(m.st, m.globalKey()), removeConstraintsOp(m.st, m.globalKey()), removeRequestedNetworksOp(m.st, m.globalKey()), annotationRemoveOp(m.st, m.globalKey()), } ifacesOps, err := m.removeNetworkInterfacesOps() if err != nil { return err } ops = append(ops, ifacesOps...) ops = append(ops, removeContainerRefOps(m.st, m.Id())...) // The only abort conditions in play indicate that the machine has already // been removed. return onAbort(m.st.runTransaction(ops), nil) }