github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/uniter/relationer.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "fmt" 8 9 "github.com/juju/juju/charm/hooks" 10 apiuniter "github.com/juju/juju/state/api/uniter" 11 "github.com/juju/juju/worker/uniter/hook" 12 "github.com/juju/juju/worker/uniter/relation" 13 ) 14 15 // Relationer manages a unit's presence in a relation. 16 type Relationer struct { 17 ctx *ContextRelation 18 ru *apiuniter.RelationUnit 19 dir *relation.StateDir 20 queue relation.HookQueue 21 hooks chan<- hook.Info 22 dying bool 23 } 24 25 // NewRelationer creates a new Relationer. The unit will not join the 26 // relation until explicitly requested. 27 func NewRelationer(ru *apiuniter.RelationUnit, dir *relation.StateDir, hooks chan<- hook.Info) *Relationer { 28 return &Relationer{ 29 ctx: NewContextRelation(ru, dir.State().Members), 30 ru: ru, 31 dir: dir, 32 hooks: hooks, 33 } 34 } 35 36 // Context returns the ContextRelation associated with r. 37 func (r *Relationer) Context() *ContextRelation { 38 return r.ctx 39 } 40 41 // IsImplicit returns whether the local relation endpoint is implicit. Implicit 42 // relations do not run hooks. 43 func (r *Relationer) IsImplicit() bool { 44 return r.ru.Endpoint().IsImplicit() 45 } 46 47 // Join initializes local state and causes the unit to enter its relation 48 // scope, allowing its counterpart units to detect its presence and settings 49 // changes. Local state directory is not created until needed. 50 func (r *Relationer) Join() error { 51 if r.dying { 52 panic("dying relationer must not join!") 53 } 54 // We need to make sure the state directory exists before we join the 55 // relation, lest a subsequent ReadAllStateDirs report local state that 56 // doesn't include relations recorded in remote state. 57 if err := r.dir.Ensure(); err != nil { 58 return err 59 } 60 // uniter.RelationUnit.EnterScope() sets the unit's private address 61 // internally automatically, so no need to set it here. 62 return r.ru.EnterScope() 63 } 64 65 // SetDying informs the relationer that the unit is departing the relation, 66 // and that the only hooks it should send henceforth are -departed hooks, 67 // until the relation is empty, followed by a -broken hook. 68 func (r *Relationer) SetDying() error { 69 if r.IsImplicit() { 70 r.dying = true 71 return r.die() 72 } 73 if r.queue != nil { 74 if err := r.StopHooks(); err != nil { 75 return err 76 } 77 defer r.StartHooks() 78 } 79 r.dying = true 80 return nil 81 } 82 83 // die is run when the relationer has no further responsibilities; it leaves 84 // relation scope, and removes the local relation state directory. 85 func (r *Relationer) die() error { 86 if err := r.ru.LeaveScope(); err != nil { 87 return err 88 } 89 return r.dir.Remove() 90 } 91 92 // StartHooks starts watching the relation, and sending hook.Info events on the 93 // hooks channel. It will panic if called when already responding to relation 94 // changes. 95 func (r *Relationer) StartHooks() error { 96 if r.IsImplicit() { 97 return nil 98 } 99 if r.queue != nil { 100 panic("hooks already started!") 101 } 102 if r.dying { 103 r.queue = relation.NewDyingHookQueue(r.dir.State(), r.hooks) 104 } else { 105 w, err := r.ru.Watch() 106 if err != nil { 107 return err 108 } 109 r.queue = relation.NewAliveHookQueue(r.dir.State(), r.hooks, w) 110 } 111 return nil 112 } 113 114 // StopHooks ensures that the relationer is not watching the relation, or sending 115 // hook.Info events on the hooks channel. 116 func (r *Relationer) StopHooks() error { 117 if r.queue == nil { 118 return nil 119 } 120 queue := r.queue 121 r.queue = nil 122 return queue.Stop() 123 } 124 125 // PrepareHook checks that the relation is in a state such that it makes 126 // sense to execute the supplied hook, and ensures that the relation context 127 // contains the latest relation state as communicated in the hook.Info. It 128 // returns the name of the hook that must be run. 129 func (r *Relationer) PrepareHook(hi hook.Info) (hookName string, err error) { 130 if r.IsImplicit() { 131 panic("implicit relations must not run hooks") 132 } 133 if err = r.dir.State().Validate(hi); err != nil { 134 return 135 } 136 if hi.Kind == hooks.RelationDeparted { 137 r.ctx.DeleteMember(hi.RemoteUnit) 138 } else if hi.RemoteUnit != "" { 139 r.ctx.UpdateMembers(SettingsMap{hi.RemoteUnit: nil}) 140 } 141 name := r.ru.Endpoint().Name 142 return fmt.Sprintf("%s-%s", name, hi.Kind), nil 143 } 144 145 // CommitHook persists the fact of the supplied hook's completion. 146 func (r *Relationer) CommitHook(hi hook.Info) error { 147 if r.IsImplicit() { 148 panic("implicit relations must not run hooks") 149 } 150 if hi.Kind == hooks.RelationBroken { 151 return r.die() 152 } 153 return r.dir.Write(hi) 154 }