// 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 trivial.ErrorContextf(&err, "cannot destroy relation %q", r) 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.runner.Run(ops, "", nil); err != txn.ErrAborted { return err } if err := rel.Refresh(); IsNotFound(err) { return nil } else if err != nil { return err } } return ErrExcessiveContention }
// RemoveUnit removes the given unit from s. func (s *Service) RemoveUnit(u *Unit) (err error) { defer trivial.ErrorContextf(&err, "cannot remove unit %q", u) if u.doc.Life != Dead { return errors.New("unit is not dead") } if u.doc.Service != s.doc.Name { return fmt.Errorf("unit is not assigned to service %q", s) } svc := &Service{s.st, s.doc} unit := &Unit{u.st, u.doc} for i := 0; i < 5; i++ { ops := svc.removeUnitOps(unit) if err := svc.st.runner.Run(ops, "", nil); err != txn.ErrAborted { return err } if err := unit.Refresh(); IsNotFound(err) { return nil } else if err != nil { return err } if err := svc.Refresh(); IsNotFound(err) { return nil } else if err != nil { return err } } return ErrExcessiveContention }
// AddUnitSubordinateTo adds a new subordinate unit to the service, subordinate // to principal. It does not verify relation state sanity or pre-existence of // other subordinates of the same service; is deprecated; and only continues // to exist for the convenience of certain tests, which are themselves due for // overhaul. func (s *Service) AddUnitSubordinateTo(principal *Unit) (unit *Unit, err error) { log.Printf("state: Service.AddUnitSubordinateTo is DEPRECATED; subordinate units should be created only as a side-effect of a principal entering relation scope") defer trivial.ErrorContextf(&err, "cannot add unit to service %q as a subordinate of %q", s, principal) ch, _, err := s.Charm() if err != nil { return nil, err } if !ch.Meta().Subordinate { return nil, fmt.Errorf("service is not a subordinate") } if !principal.IsPrincipal() { return nil, fmt.Errorf("unit is not a principal") } name, ops, err := s.addUnitOps(principal.doc.Name, false) if err != nil { return nil, err } if err = s.st.runner.Run(ops, "", nil); err == nil { return s.Unit(name) } else if err != txn.ErrAborted { return nil, err } 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") } if alive, err := isAlive(s.st.units, principal.doc.Name); err != nil { return nil, err } else if !alive { return nil, fmt.Errorf("principal unit is not alive") } return nil, fmt.Errorf("inconsistent state") }
// validate returns an error if the state violates expectations. func (st State) validate() (err error) { defer trivial.ErrorContextf(&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 }
// 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 trivial.ErrorContextf(&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 AssignUnused: if _, err = u.AssignToUnusedMachine(); err != noUnusedMachines { return err } for { // TODO(rog) take out a lease on the new machine // so that we don't have a race here. m, err := st.AddMachine(JobHostUnits) if err != nil { return err } err = u.assignToMachine(m, true) if err == inUseErr { // Someone else has grabbed the machine we've // just allocated, so try again. continue } return err } } panic(fmt.Errorf("unknown unit assignment policy: %q", policy)) }
// RemoveMachine removes the machine with the the given id. func (st *State) RemoveMachine(id string) (err error) { defer trivial.ErrorContextf(&err, "cannot remove machine %s", id) m, err := st.Machine(id) if err != nil { return err } if m.doc.Life != Dead { return fmt.Errorf("machine is not dead") } sel := D{ {"_id", id}, {"life", Dead}, } ops := []txn.Op{{ C: st.machines.Name, Id: id, Assert: sel, Remove: true, }} if err := st.runner.Run(ops, "", nil); err != nil { // If aborted, the machine is either dead or recreated. return onAbort(err, nil) } return nil }
// 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 trivial.ErrorContextf(&err, "failed to write %q hook info for %q on state directory", hi.Kind, hi.RemoteUnit) if hi.Kind == hook.RelationBroken { return d.Remove() } name := strings.Replace(hi.RemoteUnit, "/", "-", 1) path := filepath.Join(d.path, name) if hi.Kind == hook.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 == hook.RelationJoined} if err := trivial.WriteYaml(path, &di); err != nil { return err } // If write was successful, update own state. d.state.Members[hi.RemoteUnit] = hi.ChangeVersion if hi.Kind == hook.RelationJoined { d.state.ChangedPending = hi.RemoteUnit } else { d.state.ChangedPending = "" } return nil }
// DestroyUnits removes the specified units from the state. func (conn *Conn) DestroyUnits(names ...string) (err error) { defer trivial.ErrorContextf(&err, "cannot destroy units") var units []*state.Unit for _, name := range names { unit, err := conn.State.Unit(name) switch { case state.IsNotFound(err): return fmt.Errorf("unit %q is not alive", name) case err != nil: return err case unit.Life() != state.Alive: return fmt.Errorf("unit %q is not alive", name) case unit.IsPrincipal(): units = append(units, unit) default: return fmt.Errorf("unit %q is a subordinate", name) } } for _, unit := range units { if err := unit.EnsureDying(); err != nil { return err } } return nil }
// 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 trivial.ErrorContextf(&err, "cannot set resolved mode for unit %q", u) switch mode { case ResolvedNone, ResolvedRetryHooks, ResolvedNoHooks: default: return fmt.Errorf("invalid error resolution mode: %q", mode) } assert := append(notDeadDoc, D{{"resolved", ResolvedNone}}...) ops := []txn.Op{{ C: u.st.units.Name, Id: u.doc.Name, Assert: assert, Update: D{{"$set", D{{"resolved", mode}}}}, }} if err := u.st.runner.Run(ops, "", nil); err != nil { if err == txn.ErrAborted { // Find which assertion failed so we can give a // more specific error. u1, err := u.st.Unit(u.Name()) if err != nil { return err } if u1.Life() != Alive { return errNotAlive } return fmt.Errorf("already resolved") } return err } u.doc.Resolved = mode return nil }
// 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 trivial.ErrorContextf(&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 == hook.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 != hook.RelationChanged { return fmt.Errorf(`expected "relation-changed" for %q`, s.ChangedPending) } } else if _, joined := s.Members[unit]; joined && kind == hook.RelationJoined { return fmt.Errorf("unit already joined") } else if !joined && kind != hook.RelationJoined { return fmt.Errorf("unit has not joined") } return nil }
func (u *Uniter) init(name string) (err error) { defer trivial.ErrorContextf(&err, "failed to initialize uniter for unit %q", name) u.unit, err = u.st.Unit(name) if err != nil { return err } ename := u.unit.EntityName() u.toolsDir = environs.AgentToolsDir(u.dataDir, ename) if err := EnsureJujucSymlinks(u.toolsDir); err != nil { return err } u.baseDir = filepath.Join(u.dataDir, "agents", ename) u.relationsDir = filepath.Join(u.baseDir, "state", "relations") if err := os.MkdirAll(u.relationsDir, 0755); err != nil { return err } u.service, err = u.st.Service(u.unit.ServiceName()) if err != nil { return err } u.relationers = map[int]*Relationer{} u.relationHooks = make(chan hook.Info) u.charm = charm.NewGitDir(filepath.Join(u.baseDir, "charm")) u.bundles = charm.NewBundlesDir(filepath.Join(u.baseDir, "state", "bundles")) u.deployer = charm.NewDeployer(filepath.Join(u.baseDir, "state", "deployer")) u.sf = NewStateFile(filepath.Join(u.baseDir, "state", "uniter")) u.rand = rand.New(rand.NewSource(time.Now().Unix())) 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 trivial.ErrorContextf(&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{s.st, s.doc} for i := 0; i < 5; i++ { ops, err := svc.destroyOps() switch { case err == errRefresh: case err == errAlreadyDying: return nil case err != nil: return err default: if err := svc.st.runner.Run(ops, "", nil); err != txn.ErrAborted { return err } } if err := svc.Refresh(); 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 trivial.ErrorContextf(&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 }
func verifyConfig(cfg *MachineConfig) (err error) { defer trivial.ErrorContextf(&err, "invalid machine configuration") if !state.IsMachineId(cfg.MachineId) { return fmt.Errorf("invalid machine id") } if cfg.ProviderType == "" { return fmt.Errorf("missing provider type") } if cfg.DataDir == "" { return fmt.Errorf("missing var directory") } if cfg.Tools == nil { return fmt.Errorf("missing tools") } if cfg.Tools.URL == "" { return fmt.Errorf("missing tools URL") } if cfg.StateInfo == nil { return fmt.Errorf("missing state info") } if len(cfg.StateInfo.CACert) == 0 { return fmt.Errorf("missing CA certificate") } if cfg.StateServer { if cfg.InstanceIdAccessor == "" { return fmt.Errorf("missing instance id accessor") } if cfg.Config == nil { return fmt.Errorf("missing environment configuration") } if cfg.StateInfo.EntityName != "" { return fmt.Errorf("entity name must be blank when starting a state server") } if len(cfg.StateServerCert) == 0 { return fmt.Errorf("missing state server certificate") } if len(cfg.StateServerKey) == 0 { return fmt.Errorf("missing state server private key") } } else { if len(cfg.StateInfo.Addrs) == 0 { return fmt.Errorf("missing state hosts") } if cfg.StateInfo.EntityName != state.MachineEntityName(cfg.MachineId) { return fmt.Errorf("entity name must match started machine") } } for _, r := range cfg.StateInfo.Password { if r == '\'' || r == '\\' || r < 32 { return fmt.Errorf("password has disallowed characters") } } return nil }
// Relations returns a Relation for every relation the service is in. func (s *Service) Relations() (relations []*Relation, err error) { defer trivial.ErrorContextf(&err, "can't get relations for service %q", s) docs := []relationDoc{} err = s.st.relations.Find(D{{"endpoints.servicename", s.doc.Name}}).All(&docs) if err != nil { return nil, err } for _, v := range docs { relations = append(relations, newRelation(s.st, &v)) } return relations, nil }
// 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 trivial.ErrorContextf(&err, "cannot read settings for unit %q in relation %q", uname, ru.relation) if !IsUnitName(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 }
// 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 trivial.ErrorContextf(&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 = trivial.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 }
// SetAgentTools sets the tools that the agent is currently running. func (u *Unit) SetAgentTools(t *Tools) (err error) { defer trivial.ErrorContextf(&err, "cannot set agent tools for unit %q", u) if t.Series == "" || t.Arch == "" { return fmt.Errorf("empty series or arch") } ops := []txn.Op{{ C: u.st.units.Name, Id: u.doc.Name, Assert: notDeadDoc, Update: D{{"$set", D{{"tools", t}}}}, }} if err := u.st.runner.Run(ops, "", nil); err != nil { return onAbort(err, errNotAlive) } tools := *t u.doc.Tools = &tools return nil }
// AddUnit adds a new principal unit to the service. func (s *Service) AddUnit() (unit *Unit, err error) { defer trivial.ErrorContextf(&err, "cannot add unit to service %q", s) name, ops, err := s.addUnitOps("", false) if err != nil { return nil, err } if err := s.st.runner.Run(ops, "", nil); 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) }
// deathFailureReason returns an error indicating why the machine may have // failed to advance its lifecycle to Dying or Dead. If deathFailureReason // returns no error, it is possible that the condition that caused the txn // failure no longer holds; it does not automatically indicate bad state. func (m *Machine) deathFailureReason(life Life) (err error) { if m, err = m.st.Machine(m.doc.Id); err != nil { return err } defer trivial.ErrorContextf(&err, "machine %s cannot become %s", m, life) for _, j := range m.doc.Jobs { if j == JobManageEnviron { // If and when we enable multiple JobManageEnviron machines, the // restriction will become "there must be at least one machine // with this job", and this will need to change. return fmt.Errorf("required by environment") } } if len(m.doc.Principals) != 0 { return fmt.Errorf("unit %q is assigned to it", m.doc.Principals[0]) } return nil }
// SetAgentTools sets the tools that the agent is currently running. func (m *Machine) SetAgentTools(t *Tools) (err error) { defer trivial.ErrorContextf(&err, "cannot set agent tools for machine %v", m) if t.Series == "" || t.Arch == "" { return fmt.Errorf("empty series or arch") } ops := []txn.Op{{ C: m.st.machines.Name, Id: m.doc.Id, Assert: notDeadDoc, Update: D{{"$set", D{{"tools", t}}}}, }} if err := m.st.runner.Run(ops, "", nil); err != nil { return onAbort(err, errNotAlive) } tools := *t m.doc.Tools = &tools return nil }
// WaitAgentAlive blocks until the respective agent is alive. func (u *Unit) WaitAgentAlive(timeout time.Duration) (err error) { defer trivial.ErrorContextf(&err, "waiting for agent of unit %q", u) ch := make(chan presence.Change) u.st.pwatcher.Watch(u.globalKey(), ch) defer u.st.pwatcher.Unwatch(u.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 <-u.st.pwatcher.Dead(): return u.st.pwatcher.Err() } } panic(fmt.Sprintf("presence reported dead status twice in a row for unit %q", u)) }
// addMachine implements AddMachine and InjectMachine. func (st *State) addMachine(instanceId InstanceId, jobs []MachineJob) (m *Machine, err error) { defer trivial.ErrorContextf(&err, "cannot add a new machine") if len(jobs) == 0 { return nil, fmt.Errorf("no jobs specified") } jset := make(map[MachineJob]bool) for _, j := range jobs { if jset[j] { return nil, fmt.Errorf("duplicate job: %s", j) } jset[j] = true } seq, err := st.sequence("machine") if err != nil { return nil, err } id := strconv.Itoa(seq) mdoc := machineDoc{ Id: id, Life: Alive, Jobs: jobs, } if instanceId != "" { mdoc.InstanceId = instanceId } ops := []txn.Op{{ C: st.machines.Name, Id: id, Assert: txn.DocMissing, Insert: mdoc, }} err = st.runner.Run(ops, "", nil) if err != nil { return nil, err } // Refresh to pick the txn-revno. m = newMachine(st, &mdoc) if err = m.Refresh(); err != nil { return nil, err } return m, nil }
// Units returns all the units that have been assigned to the machine. func (m *Machine) Units() (units []*Unit, err error) { defer trivial.ErrorContextf(&err, "cannot get units assigned to machine %v", m) pudocs := []unitDoc{} err = m.st.units.Find(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(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 }
// 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(sch *state.Charm, abort <-chan struct{}) (err error) { defer trivial.ErrorContextf(&err, "failed to download charm %q from %q", sch.URL(), sch.BundleURL()) dir := d.downloadsPath() if err := os.MkdirAll(dir, 0755); err != nil { return err } burl := sch.BundleURL().String() log.Printf("worker/uniter/charm: downloading %s from %s", sch.URL(), burl) dl := downloader.New(burl, dir) defer dl.Stop() for { select { case <-abort: log.Printf("worker/uniter/charm: download aborted") return fmt.Errorf("aborted") case st := <-dl.Done(): if st.Err != nil { return st.Err } log.Printf("worker/uniter/charm: download complete") defer st.File.Close() hash := sha256.New() if _, err = io.Copy(hash, st.File); err != nil { return err } actualSha256 := hex.EncodeToString(hash.Sum(nil)) if actualSha256 != sch.BundleSha256() { return fmt.Errorf( "expected sha256 %q, got %q", sch.BundleSha256(), actualSha256, ) } log.Printf("worker/uniter/charm: download verified") if err := os.MkdirAll(d.path, 0755); err != nil { return err } return os.Rename(st.File.Name(), d.bundlePath(sch)) } } panic("unreachable") }
// 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 := Port{Protocol: protocol, Number: number} defer trivial.ErrorContextf(&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: D{{"$pull", D{{"ports", port}}}}, }} err = u.st.runner.Run(ops, "", nil) if err != nil { return onAbort(err, errNotAlive) } newPorts := make([]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 }
// ensureDead advances the specified entity's life status to Dead, if necessary. // Preconditions can be supplied in assertOps; if the preconditions fail, the error // will contain assertMsg. If the entity is not found, no error is returned. func ensureDead(st *State, coll *mgo.Collection, id interface{}, desc string, assertOps []txn.Op, assertMsg string) (err error) { defer trivial.ErrorContextf(&err, "cannot finish termination of %s %#v", desc, id) ops := append(assertOps, txn.Op{ C: coll.Name, Id: id, Update: D{{"$set", D{{"life", Dead}}}}, }) if err = st.runner.Run(ops, "", nil); err == nil { return nil } else if err != txn.ErrAborted { return err } var doc struct{ Life } if err = coll.FindId(id).One(&doc); err == mgo.ErrNotFound { return nil } else if err != nil { return err } else if doc.Life != Dead { return fmt.Errorf(assertMsg) } return nil }
// fetchMetadata fetches a single atom of data from the ec2 instance metadata service. // http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html func fetchMetadata(name string) (value string, err error) { uri := fmt.Sprintf("%s/2011-01-01/meta-data/%s", metadataHost, name) defer trivial.ErrorContextf(&err, "cannot get %q", uri) for a := shortAttempt.Start(); a.Next(); { var resp *http.Response resp, err = http.Get(uri) if err != nil { continue } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { err = fmt.Errorf("bad http response %v", resp.Status) continue } var data []byte data, err = ioutil.ReadAll(resp.Body) if err != nil { continue } return strings.TrimSpace(string(data)), nil } return }
// OpenPort sets the policy of the port with protocol and number to be opened. func (u *Unit) OpenPort(protocol string, number int) (err error) { port := Port{Protocol: protocol, Number: number} defer trivial.ErrorContextf(&err, "cannot open port %v for unit %q", port, u) ops := []txn.Op{{ C: u.st.units.Name, Id: u.doc.Name, Assert: notDeadDoc, Update: D{{"$addToSet", D{{"ports", port}}}}, }} err = u.st.runner.Run(ops, "", nil) if err != nil { return onAbort(err, errNotAlive) } found := false for _, p := range u.doc.Ports { if p == port { break } } if !found { u.doc.Ports = append(u.doc.Ports, port) } return nil }
// AddRelation creates a new relation with the given endpoints. func (st *State) AddRelation(endpoints ...Endpoint) (r *Relation, err error) { defer trivial.ErrorContextf(&err, "cannot add relation %q", relationKey(endpoints)) switch len(endpoints) { case 1: if endpoints[0].RelationRole != RolePeer { return nil, fmt.Errorf("single endpoint must be a peer relation") } case 2: if !endpoints[0].CanRelateTo(endpoints[1]) { return nil, fmt.Errorf("endpoints do not relate") } default: return nil, fmt.Errorf("cannot relate %d endpoints", len(endpoints)) } ops := []txn.Op{} var scope charm.RelationScope for _, v := range endpoints { if v.RelationScope == charm.ScopeContainer { scope = charm.ScopeContainer } ops = append(ops, txn.Op{ C: st.services.Name, Id: v.ServiceName, Assert: isAliveDoc, Update: D{{"$inc", D{{"relationcount", 1}}}}, }) } if scope == charm.ScopeContainer { for i := range endpoints { endpoints[i].RelationScope = scope } } id, err := st.sequence("relation") if err != nil { return nil, err } doc := relationDoc{ Key: relationKey(endpoints), Id: id, Endpoints: endpoints, Life: Alive, } ops = append(ops, txn.Op{ C: st.relations.Name, Id: doc.Key, Assert: txn.DocMissing, Insert: doc, }) err = st.runner.Run(ops, "", nil) if err == txn.ErrAborted { for _, ep := range endpoints { svc, err := st.Service(ep.ServiceName) if IsNotFound(err) || svc.Life() != Alive { return nil, fmt.Errorf("service %q is not alive", ep.ServiceName) } else if err != nil { return nil, err } } return nil, fmt.Errorf("relation already exists") } else if err != nil { return nil, err } return newRelation(st, &doc), nil }