github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 "gopkg.in/juju/charm.v5/hooks" 10 11 apiuniter "github.com/juju/juju/api/uniter" 12 "github.com/juju/juju/worker/uniter/hook" 13 "github.com/juju/juju/worker/uniter/relation" 14 "github.com/juju/juju/worker/uniter/runner" 15 ) 16 17 // Relationer manages a unit's presence in a relation. 18 type Relationer struct { 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 ru: ru, 31 dir: dir, 32 hooks: hooks, 33 } 34 } 35 36 // ContextInfo returns a represention of r's current state. 37 func (r *Relationer) ContextInfo() *runner.RelationInfo { 38 members := r.dir.State().Members 39 memberNames := make([]string, 0, len(members)) 40 for memberName := range members { 41 memberNames = append(memberNames, memberName) 42 } 43 return &runner.RelationInfo{r.ru, memberNames} 44 } 45 46 // IsImplicit returns whether the local relation endpoint is implicit. Implicit 47 // relations do not run hooks. 48 func (r *Relationer) IsImplicit() bool { 49 return r.ru.Endpoint().IsImplicit() 50 } 51 52 // Join initializes local state and causes the unit to enter its relation 53 // scope, allowing its counterpart units to detect its presence and settings 54 // changes. Local state directory is not created until needed. 55 func (r *Relationer) Join() error { 56 if r.dying { 57 panic("dying relationer must not join!") 58 } 59 // We need to make sure the state directory exists before we join the 60 // relation, lest a subsequent ReadAllStateDirs report local state that 61 // doesn't include relations recorded in remote state. 62 if err := r.dir.Ensure(); err != nil { 63 return err 64 } 65 // uniter.RelationUnit.EnterScope() sets the unit's private address 66 // internally automatically, so no need to set it here. 67 return r.ru.EnterScope() 68 } 69 70 // SetDying informs the relationer that the unit is departing the relation, 71 // and that the only hooks it should send henceforth are -departed hooks, 72 // until the relation is empty, followed by a -broken hook. 73 func (r *Relationer) SetDying() error { 74 if r.IsImplicit() { 75 r.dying = true 76 return r.die() 77 } 78 if r.queue != nil { 79 if err := r.StopHooks(); err != nil { 80 return err 81 } 82 defer r.StartHooks() 83 } 84 r.dying = true 85 return nil 86 } 87 88 // die is run when the relationer has no further responsibilities; it leaves 89 // relation scope, and removes the local relation state directory. 90 func (r *Relationer) die() error { 91 if err := r.ru.LeaveScope(); err != nil { 92 return err 93 } 94 return r.dir.Remove() 95 } 96 97 // StartHooks starts watching the relation, and sending hook.Info events on the 98 // hooks channel. It will panic if called when already responding to relation 99 // changes. 100 func (r *Relationer) StartHooks() error { 101 if r.IsImplicit() { 102 return nil 103 } 104 if r.queue != nil { 105 panic("hooks already started!") 106 } 107 if r.dying { 108 r.queue = relation.NewDyingHookQueue(r.dir.State(), r.hooks) 109 } else { 110 w, err := r.ru.Watch() 111 if err != nil { 112 return err 113 } 114 r.queue = relation.NewAliveHookQueue(r.dir.State(), r.hooks, w) 115 } 116 return nil 117 } 118 119 // StopHooks ensures that the relationer is not watching the relation, or sending 120 // hook.Info events on the hooks channel. 121 func (r *Relationer) StopHooks() error { 122 if r.queue == nil { 123 return nil 124 } 125 queue := r.queue 126 r.queue = nil 127 return queue.Stop() 128 } 129 130 // PrepareHook checks that the relation is in a state such that it makes 131 // sense to execute the supplied hook, and ensures that the relation context 132 // contains the latest relation state as communicated in the hook.Info. It 133 // returns the name of the hook that must be run. 134 func (r *Relationer) PrepareHook(hi hook.Info) (hookName string, err error) { 135 if r.IsImplicit() { 136 panic("implicit relations must not run hooks") 137 } 138 if err = r.dir.State().Validate(hi); err != nil { 139 return 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 }