github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/relation/relationer.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package relation 5 6 import ( 7 "fmt" 8 9 "github.com/juju/errors" 10 "gopkg.in/juju/charm.v6/hooks" 11 12 apiuniter "github.com/juju/juju/api/uniter" 13 "github.com/juju/juju/worker/uniter/hook" 14 "github.com/juju/juju/worker/uniter/runner/context" 15 ) 16 17 // Relationer manages a unit's presence in a relation. 18 type Relationer struct { 19 ru *apiuniter.RelationUnit 20 dir *StateDir 21 dying bool 22 } 23 24 // NewRelationer creates a new Relationer. The unit will not join the 25 // relation until explicitly requested. 26 func NewRelationer(ru *apiuniter.RelationUnit, dir *StateDir) *Relationer { 27 return &Relationer{ 28 ru: ru, 29 dir: dir, 30 } 31 } 32 33 // ContextInfo returns a representation of the Relationer's current state. 34 func (r *Relationer) ContextInfo() *context.RelationInfo { 35 members := r.dir.State().Members 36 memberNames := make([]string, 0, len(members)) 37 for memberName := range members { 38 memberNames = append(memberNames, memberName) 39 } 40 return &context.RelationInfo{r.ru, memberNames} 41 } 42 43 // IsImplicit returns whether the local relation endpoint is implicit. Implicit 44 // relations do not run hooks. 45 func (r *Relationer) IsImplicit() bool { 46 return r.ru.Endpoint().IsImplicit() 47 } 48 49 // Join initializes local state and causes the unit to enter its relation 50 // scope, allowing its counterpart units to detect its presence and settings 51 // changes. Local state directory is not created until needed. 52 func (r *Relationer) Join() error { 53 if r.dying { 54 panic("dying relationer must not join!") 55 } 56 // We need to make sure the state directory exists before we join the 57 // relation, lest a subsequent ReadAllStateDirs report local state that 58 // doesn't include relations recorded in remote state. 59 if err := r.dir.Ensure(); err != nil { 60 return err 61 } 62 // uniter.RelationUnit.EnterScope() sets the unit's private address 63 // internally automatically, so no need to set it here. 64 return r.ru.EnterScope() 65 } 66 67 // SetDying informs the relationer that the unit is departing the relation, 68 // and that the only hooks it should send henceforth are -departed hooks, 69 // until the relation is empty, followed by a -broken hook. 70 func (r *Relationer) SetDying() error { 71 if r.IsImplicit() { 72 r.dying = true 73 return r.die() 74 } 75 r.dying = true 76 return nil 77 } 78 79 // die is run when the relationer has no further responsibilities; it leaves 80 // relation scope, and removes the local relation state directory. 81 func (r *Relationer) die() error { 82 if err := r.ru.LeaveScope(); err != nil { 83 return errors.Annotatef(err, "leaving scope of relation %q", r.ru.Relation()) 84 } 85 return r.dir.Remove() 86 } 87 88 // PrepareHook checks that the relation is in a state such that it makes 89 // sense to execute the supplied hook, and ensures that the relation context 90 // contains the latest relation state as communicated in the hook.Info. It 91 // returns the name of the hook that must be run. 92 func (r *Relationer) PrepareHook(hi hook.Info) (hookName string, err error) { 93 if r.IsImplicit() { 94 panic("implicit relations must not run hooks") 95 } 96 if err = r.dir.State().Validate(hi); err != nil { 97 return 98 } 99 name := r.ru.Endpoint().Name 100 return fmt.Sprintf("%s-%s", name, hi.Kind), nil 101 } 102 103 // CommitHook persists the fact of the supplied hook's completion. 104 func (r *Relationer) CommitHook(hi hook.Info) error { 105 if r.IsImplicit() { 106 panic("implicit relations must not run hooks") 107 } 108 if hi.Kind == hooks.RelationBroken { 109 return r.die() 110 } 111 return r.dir.Write(hi) 112 }