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