github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/relations.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/names" 9 corecharm "gopkg.in/juju/charm.v5" 10 "gopkg.in/juju/charm.v5/hooks" 11 "launchpad.net/tomb" 12 13 "github.com/juju/juju/api/uniter" 14 "github.com/juju/juju/apiserver/params" 15 "github.com/juju/juju/state/watcher" 16 "github.com/juju/juju/worker/uniter/hook" 17 "github.com/juju/juju/worker/uniter/relation" 18 "github.com/juju/juju/worker/uniter/runner" 19 ) 20 21 // Relations exists to encapsulate relation state and operations behind an 22 // interface for the benefit of future refactoring. 23 type Relations interface { 24 25 // Name returns the name of the relation with the supplied id, or an error 26 // if the relation is unknown. 27 Name(id int) (string, error) 28 29 // Hooks returns the channel on which relation hook execution requests 30 // are sent. 31 Hooks() <-chan hook.Info 32 33 // StartHooks starts sending hook execution requests on the Hooks channel. 34 StartHooks() 35 36 // StopHooks stops sending hook execution requests on the Hooks channel. 37 StopHooks() error 38 39 // PrepareHook returns the name of the supplied relation hook, or an error 40 // if the hook is unknown or invalid given current state. 41 PrepareHook(hookInfo hook.Info) (string, error) 42 43 // CommitHook persists the state change encoded in the supplied relation 44 // hook, or returns an error if the hook is unknown or invalid given 45 // current relation state. 46 CommitHook(hookInfo hook.Info) error 47 48 // GetInfo returns information about current relation state. 49 GetInfo() map[int]*runner.RelationInfo 50 51 // Update checks for and responds to changes in the life states of the 52 // relations with the supplied ids. If any id corresponds to an alive 53 // relation that is not already recorded, the unit will enter scope for 54 // that relation and start its hook queue. 55 Update(ids []int) error 56 57 // SetDying notifies all known relations that the only hooks to be requested 58 // should be those necessary to cleanly exit the relation. 59 SetDying() error 60 } 61 62 // relations implements Relations. 63 type relations struct { 64 st *uniter.State 65 unit *uniter.Unit 66 charmDir string 67 relationsDir string 68 relationers map[int]*Relationer 69 relationHooks chan hook.Info 70 abort <-chan struct{} 71 } 72 73 func newRelations(st *uniter.State, tag names.UnitTag, paths Paths, abort <-chan struct{}) (*relations, error) { 74 unit, err := st.Unit(tag) 75 if err != nil { 76 return nil, errors.Trace(err) 77 } 78 r := &relations{ 79 st: st, 80 unit: unit, 81 charmDir: paths.State.CharmDir, 82 relationsDir: paths.State.RelationsDir, 83 relationers: make(map[int]*Relationer), 84 relationHooks: make(chan hook.Info), 85 abort: abort, 86 } 87 if err := r.init(); err != nil { 88 return nil, errors.Trace(err) 89 } 90 return r, nil 91 } 92 93 // init reconciles the local relation state dirs with the remote state of 94 // the corresponding relations. It's only expected to be called while a 95 // *relations is being created. 96 func (r *relations) init() error { 97 joinedRelationTags, err := r.unit.JoinedRelations() 98 if err != nil { 99 return errors.Trace(err) 100 } 101 joinedRelations := make(map[int]*uniter.Relation) 102 for _, tag := range joinedRelationTags { 103 relation, err := r.st.Relation(tag) 104 if err != nil { 105 return errors.Trace(err) 106 } 107 joinedRelations[relation.Id()] = relation 108 } 109 knownDirs, err := relation.ReadAllStateDirs(r.relationsDir) 110 if err != nil { 111 return errors.Trace(err) 112 } 113 for id, dir := range knownDirs { 114 if rel, ok := joinedRelations[id]; ok { 115 if err := r.add(rel, dir); err != nil { 116 return errors.Trace(err) 117 } 118 } else if err := dir.Remove(); err != nil { 119 return errors.Trace(err) 120 } 121 } 122 for id, rel := range joinedRelations { 123 if _, ok := knownDirs[id]; ok { 124 continue 125 } 126 dir, err := relation.ReadStateDir(r.relationsDir, id) 127 if err != nil { 128 return errors.Trace(err) 129 } 130 if err := r.add(rel, dir); err != nil { 131 return errors.Trace(err) 132 } 133 } 134 return nil 135 } 136 137 // Name is part of the Relations interface. 138 func (r *relations) Name(id int) (string, error) { 139 relationer, found := r.relationers[id] 140 if !found { 141 return "", errors.Errorf("unknown relation: %d", id) 142 } 143 return relationer.ru.Endpoint().Name, nil 144 } 145 146 // Hooks is part of the Relations interface. 147 func (r *relations) Hooks() <-chan hook.Info { 148 return r.relationHooks 149 } 150 151 // StartHooks is part of the Relations interface. 152 func (r *relations) StartHooks() { 153 for _, relationer := range r.relationers { 154 relationer.StartHooks() 155 } 156 } 157 158 // StopHooks is part of the Relations interface. 159 func (r *relations) StopHooks() (err error) { 160 for _, relationer := range r.relationers { 161 if e := relationer.StopHooks(); e != nil { 162 if err == nil { 163 err = e 164 } else { 165 logger.Errorf("additional error while stopping hooks: %v", e) 166 } 167 } 168 } 169 return err 170 } 171 172 // PrepareHook is part of the Relations interface. 173 func (r *relations) PrepareHook(hookInfo hook.Info) (string, error) { 174 if !hookInfo.Kind.IsRelation() { 175 return "", errors.Errorf("not a relation hook: %#v", hookInfo) 176 } 177 relationer, found := r.relationers[hookInfo.RelationId] 178 if !found { 179 return "", errors.Errorf("unknown relation: %d", hookInfo.RelationId) 180 } 181 return relationer.PrepareHook(hookInfo) 182 } 183 184 // CommitHook is part of the Relations interface. 185 func (r *relations) CommitHook(hookInfo hook.Info) error { 186 if !hookInfo.Kind.IsRelation() { 187 return errors.Errorf("not a relation hook: %#v", hookInfo) 188 } 189 relationer, found := r.relationers[hookInfo.RelationId] 190 if !found { 191 return errors.Errorf("unknown relation: %d", hookInfo.RelationId) 192 } 193 if hookInfo.Kind == hooks.RelationBroken { 194 delete(r.relationers, hookInfo.RelationId) 195 } 196 return relationer.CommitHook(hookInfo) 197 } 198 199 // GetInfo is part of the Relations interface. 200 func (r *relations) GetInfo() map[int]*runner.RelationInfo { 201 relationInfos := map[int]*runner.RelationInfo{} 202 for id, relationer := range r.relationers { 203 relationInfos[id] = relationer.ContextInfo() 204 } 205 return relationInfos 206 } 207 208 // Update is part of the Relations interface. 209 func (r *relations) Update(ids []int) error { 210 for _, id := range ids { 211 if relationer, found := r.relationers[id]; found { 212 rel := relationer.ru.Relation() 213 if err := rel.Refresh(); err != nil { 214 return errors.Annotatef(err, "cannot update relation %q", rel) 215 } 216 if rel.Life() == params.Dying { 217 if err := r.setDying(id); err != nil { 218 return errors.Trace(err) 219 } 220 } 221 continue 222 } 223 // Relations that are not alive are simply skipped, because they 224 // were not previously known anyway. 225 rel, err := r.st.RelationById(id) 226 if err != nil { 227 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 228 continue 229 } 230 return errors.Trace(err) 231 } 232 if rel.Life() != params.Alive { 233 continue 234 } 235 // Make sure we ignore relations not implemented by the unit's charm. 236 ch, err := corecharm.ReadCharmDir(r.charmDir) 237 if err != nil { 238 return errors.Trace(err) 239 } 240 if ep, err := rel.Endpoint(); err != nil { 241 return errors.Trace(err) 242 } else if !ep.ImplementedBy(ch) { 243 logger.Warningf("skipping relation with unknown endpoint %q", ep.Name) 244 continue 245 } 246 dir, err := relation.ReadStateDir(r.relationsDir, id) 247 if err != nil { 248 return errors.Trace(err) 249 } 250 err = r.add(rel, dir) 251 if err == nil { 252 r.relationers[id].StartHooks() 253 continue 254 } 255 e := dir.Remove() 256 if !params.IsCodeCannotEnterScope(err) { 257 return errors.Trace(err) 258 } 259 if e != nil { 260 return errors.Trace(e) 261 } 262 } 263 if ok, err := r.unit.IsPrincipal(); err != nil { 264 return errors.Trace(err) 265 } else if ok { 266 return nil 267 } 268 // If no Alive relations remain between a subordinate unit's service 269 // and its principal's service, the subordinate must become Dying. 270 for _, relationer := range r.relationers { 271 scope := relationer.ru.Endpoint().Scope 272 if scope == corecharm.ScopeContainer && !relationer.dying { 273 return nil 274 } 275 } 276 return r.unit.Destroy() 277 } 278 279 // SetDying is part of the Relations interface. 280 // should be those necessary to cleanly exit the relation. 281 func (r *relations) SetDying() error { 282 for id := range r.relationers { 283 if err := r.setDying(id); err != nil { 284 return err 285 } 286 } 287 return nil 288 } 289 290 // add causes the unit agent to join the supplied relation, and to 291 // store persistent state in the supplied dir. 292 func (r *relations) add(rel *uniter.Relation, dir *relation.StateDir) (err error) { 293 logger.Infof("joining relation %q", rel) 294 ru, err := rel.Unit(r.unit) 295 if err != nil { 296 return errors.Trace(err) 297 } 298 relationer := NewRelationer(ru, dir, r.relationHooks) 299 w, err := r.unit.Watch() 300 if err != nil { 301 return errors.Trace(err) 302 } 303 defer func() { 304 if e := w.Stop(); e != nil { 305 if err == nil { 306 err = e 307 } else { 308 logger.Errorf("error stopping unit watcher: %v", e) 309 } 310 } 311 }() 312 for { 313 select { 314 case <-r.abort: 315 return tomb.ErrDying 316 case _, ok := <-w.Changes(): 317 if !ok { 318 return watcher.EnsureErr(w) 319 } 320 err := relationer.Join() 321 if params.IsCodeCannotEnterScopeYet(err) { 322 logger.Infof("cannot enter scope for relation %q; waiting for subordinate to be removed", rel) 323 continue 324 } else if err != nil { 325 return errors.Trace(err) 326 } 327 logger.Infof("joined relation %q", rel) 328 r.relationers[rel.Id()] = relationer 329 return nil 330 } 331 } 332 } 333 334 // setDying notifies the relationer identified by the supplied id that the 335 // only hook executions to be requested should be those necessary to cleanly 336 // exit the relation. 337 func (r *relations) setDying(id int) error { 338 relationer, found := r.relationers[id] 339 if !found { 340 return nil 341 } 342 if err := relationer.SetDying(); err != nil { 343 return errors.Trace(err) 344 } 345 if relationer.IsImplicit() { 346 delete(r.relationers, id) 347 } 348 return nil 349 }