launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "launchpad.net/juju-core/charm/hooks" 10 apiuniter "launchpad.net/juju-core/state/api/uniter" 11 "launchpad.net/juju-core/worker/uniter/hook" 12 "launchpad.net/juju-core/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 // uniter.RelationUnit.EnterScope() sets the unit's private address 55 // internally automatically, so no need to set it here. 56 return r.ru.EnterScope() 57 } 58 59 // SetDying informs the relationer that the unit is departing the relation, 60 // and that the only hooks it should send henceforth are -departed hooks, 61 // until the relation is empty, followed by a -broken hook. 62 func (r *Relationer) SetDying() error { 63 if r.IsImplicit() { 64 r.dying = true 65 return r.die() 66 } 67 if r.queue != nil { 68 if err := r.StopHooks(); err != nil { 69 return mask(err) 70 } 71 defer r.StartHooks() 72 } 73 r.dying = true 74 return nil 75 } 76 77 // die is run when the relationer has no further responsibilities; it leaves 78 // relation scope, and removes the local relation state directory. 79 func (r *Relationer) die() error { 80 if err := r.ru.LeaveScope(); err != nil { 81 return mask(err) 82 } 83 return r.dir.Remove() 84 } 85 86 // StartHooks starts watching the relation, and sending hook.Info events on the 87 // hooks channel. It will panic if called when already responding to relation 88 // changes. 89 func (r *Relationer) StartHooks() error { 90 if r.IsImplicit() { 91 return nil 92 } 93 if r.queue != nil { 94 panic("hooks already started!") 95 } 96 if r.dying { 97 r.queue = relation.NewDyingHookQueue(r.dir.State(), r.hooks) 98 } else { 99 w, err := r.ru.Watch() 100 if err != nil { 101 return mask(err) 102 } 103 r.queue = relation.NewAliveHookQueue(r.dir.State(), r.hooks, w) 104 } 105 return nil 106 } 107 108 // StopHooks ensures that the relationer is not watching the relation, or sending 109 // hook.Info events on the hooks channel. 110 func (r *Relationer) StopHooks() error { 111 if r.queue == nil { 112 return nil 113 } 114 queue := r.queue 115 r.queue = nil 116 return queue.Stop() 117 } 118 119 // PrepareHook checks that the relation is in a state such that it makes 120 // sense to execute the supplied hook, and ensures that the relation context 121 // contains the latest relation state as communicated in the hook.Info. It 122 // returns the name of the hook that must be run. 123 func (r *Relationer) PrepareHook(hi hook.Info) (hookName string, err error) { 124 if r.IsImplicit() { 125 panic("implicit relations must not run hooks") 126 } 127 if err = r.dir.State().Validate(hi); err != nil { 128 return 129 } 130 // We are about to use the dir, ensure it's there. 131 if err = r.dir.Ensure(); err != nil { 132 return 133 } 134 if hi.Kind == hooks.RelationDeparted { 135 r.ctx.DeleteMember(hi.RemoteUnit) 136 } else if hi.RemoteUnit != "" { 137 r.ctx.UpdateMembers(SettingsMap{hi.RemoteUnit: nil}) 138 } 139 name := r.ru.Endpoint().Name 140 return fmt.Sprintf("%s-%s", name, hi.Kind), nil 141 } 142 143 // CommitHook persists the fact of the supplied hook's completion. 144 func (r *Relationer) CommitHook(hi hook.Info) error { 145 if r.IsImplicit() { 146 panic("implicit relations must not run hooks") 147 } 148 if hi.Kind == hooks.RelationBroken { 149 return r.die() 150 } 151 return r.dir.Write(hi) 152 }