github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/uniter/relation/relations.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package relation 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/loggo" 9 "github.com/juju/names" 10 "github.com/juju/utils/set" 11 corecharm "gopkg.in/juju/charm.v6-unstable" 12 "gopkg.in/juju/charm.v6-unstable/hooks" 13 "launchpad.net/tomb" 14 15 "github.com/juju/juju/api/uniter" 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/state/watcher" 18 "github.com/juju/juju/worker/uniter/hook" 19 "github.com/juju/juju/worker/uniter/operation" 20 "github.com/juju/juju/worker/uniter/remotestate" 21 "github.com/juju/juju/worker/uniter/resolver" 22 "github.com/juju/juju/worker/uniter/runner/context" 23 ) 24 25 var logger = loggo.GetLogger("juju.worker.uniter.relation") 26 27 // Relations exists to encapsulate relation state and operations behind an 28 // interface for the benefit of future refactoring. 29 type Relations interface { 30 // Name returns the name of the relation with the supplied id, or an error 31 // if the relation is unknown. 32 Name(id int) (string, error) 33 34 // PrepareHook returns the name of the supplied relation hook, or an error 35 // if the hook is unknown or invalid given current state. 36 PrepareHook(hookInfo hook.Info) (string, error) 37 38 // CommitHook persists the state change encoded in the supplied relation 39 // hook, or returns an error if the hook is unknown or invalid given 40 // current relation state. 41 CommitHook(hookInfo hook.Info) error 42 43 // GetInfo returns information about current relation state. 44 GetInfo() map[int]*context.RelationInfo 45 46 // NextHook returns details on the next hook to execute, based on the local 47 // and remote states. 48 NextHook(resolver.LocalState, remotestate.Snapshot) (hook.Info, error) 49 } 50 51 // NewRelationsResolver returns a new Resolver that handles differences in 52 // relation state. 53 func NewRelationsResolver(r Relations) resolver.Resolver { 54 return &relationsResolver{r} 55 } 56 57 type relationsResolver struct { 58 relations Relations 59 } 60 61 // NextOp implements resolver.Resolver. 62 func (s *relationsResolver) NextOp( 63 localState resolver.LocalState, 64 remoteState remotestate.Snapshot, 65 opFactory operation.Factory, 66 ) (operation.Operation, error) { 67 hook, err := s.relations.NextHook(localState, remoteState) 68 if err != nil { 69 return nil, errors.Trace(err) 70 } 71 return opFactory.NewRunHook(hook) 72 } 73 74 // relations implements Relations. 75 type relations struct { 76 st *uniter.State 77 unit *uniter.Unit 78 charmDir string 79 relationsDir string 80 relationers map[int]*Relationer 81 abort <-chan struct{} 82 } 83 84 // NewRelations returns a new Relations instance. 85 func NewRelations(st *uniter.State, tag names.UnitTag, charmDir, relationsDir string, abort <-chan struct{}) (Relations, error) { 86 unit, err := st.Unit(tag) 87 if err != nil { 88 return nil, errors.Trace(err) 89 } 90 r := &relations{ 91 st: st, 92 unit: unit, 93 charmDir: charmDir, 94 relationsDir: relationsDir, 95 relationers: make(map[int]*Relationer), 96 abort: abort, 97 } 98 if err := r.init(); err != nil { 99 return nil, errors.Trace(err) 100 } 101 return r, nil 102 } 103 104 // init reconciles the local relation state dirs with the remote state of 105 // the corresponding relations. It's only expected to be called while a 106 // *relations is being created. 107 func (r *relations) init() error { 108 joinedRelationTags, err := r.unit.JoinedRelations() 109 if err != nil { 110 return errors.Trace(err) 111 } 112 joinedRelations := make(map[int]*uniter.Relation) 113 for _, tag := range joinedRelationTags { 114 relation, err := r.st.Relation(tag) 115 if err != nil { 116 return errors.Trace(err) 117 } 118 joinedRelations[relation.Id()] = relation 119 } 120 knownDirs, err := ReadAllStateDirs(r.relationsDir) 121 if err != nil { 122 return errors.Trace(err) 123 } 124 for id, dir := range knownDirs { 125 if rel, ok := joinedRelations[id]; ok { 126 if err := r.add(rel, dir); err != nil { 127 return errors.Trace(err) 128 } 129 } else if err := dir.Remove(); err != nil { 130 return errors.Trace(err) 131 } 132 } 133 for id, rel := range joinedRelations { 134 if _, ok := knownDirs[id]; ok { 135 continue 136 } 137 dir, err := ReadStateDir(r.relationsDir, id) 138 if err != nil { 139 return errors.Trace(err) 140 } 141 if err := r.add(rel, dir); err != nil { 142 return errors.Trace(err) 143 } 144 } 145 return nil 146 } 147 148 // NextHook implements Relations. 149 func (r *relations) NextHook( 150 localState resolver.LocalState, 151 remoteState remotestate.Snapshot, 152 ) (hook.Info, error) { 153 154 if remoteState.Life == params.Dying { 155 // The unit is Dying, so make sure all subordinates are dying. 156 var destroyAllSubordinates bool 157 for relationId, relationSnapshot := range remoteState.Relations { 158 if relationSnapshot.Life != params.Alive { 159 continue 160 } 161 relationer, ok := r.relationers[relationId] 162 if !ok { 163 continue 164 } 165 if relationer.ru.Endpoint().Scope == corecharm.ScopeContainer { 166 relationSnapshot.Life = params.Dying 167 remoteState.Relations[relationId] = relationSnapshot 168 destroyAllSubordinates = true 169 } 170 } 171 if destroyAllSubordinates { 172 if err := r.unit.DestroyAllSubordinates(); err != nil { 173 return hook.Info{}, errors.Trace(err) 174 } 175 } 176 } 177 178 // Add/remove local relation state; enter and leave scope as necessary. 179 if err := r.update(remoteState.Relations); err != nil { 180 return hook.Info{}, errors.Trace(err) 181 } 182 183 if localState.Kind != operation.Continue { 184 return hook.Info{}, resolver.ErrNoOperation 185 } 186 187 // See if any of the relations have operations to perform. 188 for relationId, relationSnapshot := range remoteState.Relations { 189 relationer, ok := r.relationers[relationId] 190 if !ok || relationer.IsImplicit() { 191 continue 192 } 193 var remoteBroken bool 194 if remoteState.Life == params.Dying || relationSnapshot.Life == params.Dying { 195 relationSnapshot = remotestate.RelationSnapshot{} 196 remoteBroken = true 197 // TODO(axw) if relation is implicit, leave scope & remove. 198 } 199 // If either the unit or the relation are Dying, 200 // then the relation should be broken. 201 hook, err := nextRelationHook(relationer.dir.State(), relationSnapshot, remoteBroken) 202 if err == resolver.ErrNoOperation { 203 continue 204 } 205 return hook, err 206 } 207 return hook.Info{}, resolver.ErrNoOperation 208 } 209 210 // nextRelationHook returns the next hook op that should be executed in the 211 // relation characterised by the supplied local and remote state; or an error 212 // if the states do not refer to the same relation; or ErrRelationUpToDate if 213 // no hooks need to be executed. 214 func nextRelationHook( 215 local *State, 216 remote remotestate.RelationSnapshot, 217 remoteBroken bool, 218 ) (hook.Info, error) { 219 220 // If there's a guaranteed next hook, return that. 221 relationId := local.RelationId 222 if local.ChangedPending != "" { 223 unitName := local.ChangedPending 224 return hook.Info{ 225 Kind: hooks.RelationChanged, 226 RelationId: relationId, 227 RemoteUnit: unitName, 228 ChangeVersion: remote.Members[unitName], 229 }, nil 230 } 231 232 // Get the union of all relevant units, and sort them, so we produce events 233 // in a consistent order (largely for the convenience of the tests). 234 allUnitNames := set.NewStrings() 235 for unitName := range local.Members { 236 allUnitNames.Add(unitName) 237 } 238 for unitName := range remote.Members { 239 allUnitNames.Add(unitName) 240 } 241 sortedUnitNames := allUnitNames.SortedValues() 242 243 // If there are any locally known units that are no longer reflected in 244 // remote state, depart them. 245 for _, unitName := range sortedUnitNames { 246 changeVersion, found := local.Members[unitName] 247 if !found { 248 continue 249 } 250 if _, found := remote.Members[unitName]; !found { 251 return hook.Info{ 252 Kind: hooks.RelationDeparted, 253 RelationId: relationId, 254 RemoteUnit: unitName, 255 ChangeVersion: changeVersion, 256 }, nil 257 } 258 } 259 260 // If the relation's meant to be broken, break it. 261 if remoteBroken { 262 return hook.Info{ 263 Kind: hooks.RelationBroken, 264 RelationId: relationId, 265 }, nil 266 } 267 268 // If there are any remote units not locally known, join them. 269 for _, unitName := range sortedUnitNames { 270 changeVersion, found := remote.Members[unitName] 271 if !found { 272 continue 273 } 274 if _, found := local.Members[unitName]; !found { 275 return hook.Info{ 276 Kind: hooks.RelationJoined, 277 RelationId: relationId, 278 RemoteUnit: unitName, 279 ChangeVersion: changeVersion, 280 }, nil 281 } 282 } 283 284 // Finally scan for remote units whose latest version is not reflected 285 // in local state. 286 for _, unitName := range sortedUnitNames { 287 remoteChangeVersion, found := remote.Members[unitName] 288 if !found { 289 continue 290 } 291 localChangeVersion, found := local.Members[unitName] 292 if !found { 293 continue 294 } 295 // NOTE(axw) we use != and not > to cater due to the 296 // use of the relation settings document's txn-revno 297 // as the version. When env-uuid migration occurs, the 298 // document is recreated, resetting txn-revno. 299 if remoteChangeVersion != localChangeVersion { 300 return hook.Info{ 301 Kind: hooks.RelationChanged, 302 RelationId: relationId, 303 RemoteUnit: unitName, 304 ChangeVersion: remoteChangeVersion, 305 }, nil 306 } 307 } 308 309 // Nothing left to do for this relation. 310 return hook.Info{}, resolver.ErrNoOperation 311 } 312 313 // Name is part of the Relations interface. 314 func (r *relations) Name(id int) (string, error) { 315 relationer, found := r.relationers[id] 316 if !found { 317 return "", errors.Errorf("unknown relation: %d", id) 318 } 319 return relationer.ru.Endpoint().Name, nil 320 } 321 322 // PrepareHook is part of the Relations interface. 323 func (r *relations) PrepareHook(hookInfo hook.Info) (string, error) { 324 if !hookInfo.Kind.IsRelation() { 325 return "", errors.Errorf("not a relation hook: %#v", hookInfo) 326 } 327 relationer, found := r.relationers[hookInfo.RelationId] 328 if !found { 329 return "", errors.Errorf("unknown relation: %d", hookInfo.RelationId) 330 } 331 return relationer.PrepareHook(hookInfo) 332 } 333 334 // CommitHook is part of the Relations interface. 335 func (r *relations) CommitHook(hookInfo hook.Info) error { 336 if !hookInfo.Kind.IsRelation() { 337 return errors.Errorf("not a relation hook: %#v", hookInfo) 338 } 339 relationer, found := r.relationers[hookInfo.RelationId] 340 if !found { 341 return errors.Errorf("unknown relation: %d", hookInfo.RelationId) 342 } 343 if hookInfo.Kind == hooks.RelationBroken { 344 delete(r.relationers, hookInfo.RelationId) 345 } 346 return relationer.CommitHook(hookInfo) 347 } 348 349 // GetInfo is part of the Relations interface. 350 func (r *relations) GetInfo() map[int]*context.RelationInfo { 351 relationInfos := map[int]*context.RelationInfo{} 352 for id, relationer := range r.relationers { 353 relationInfos[id] = relationer.ContextInfo() 354 } 355 return relationInfos 356 } 357 358 func (r *relations) update(remote map[int]remotestate.RelationSnapshot) error { 359 for id, relationSnapshot := range remote { 360 if _, found := r.relationers[id]; found { 361 // We've seen this relation before. The only changes 362 // we care about are to the lifecycle state, and to 363 // the member settings versions. We handle differences 364 // in settings in nextRelationHook. 365 if relationSnapshot.Life == params.Dying { 366 if err := r.setDying(id); err != nil { 367 return errors.Trace(err) 368 } 369 } 370 continue 371 } 372 // Relations that are not alive are simply skipped, because they 373 // were not previously known anyway. 374 if relationSnapshot.Life != params.Alive { 375 continue 376 } 377 rel, err := r.st.RelationById(id) 378 if err != nil { 379 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 380 continue 381 } 382 return errors.Trace(err) 383 } 384 // Make sure we ignore relations not implemented by the unit's charm. 385 ch, err := corecharm.ReadCharmDir(r.charmDir) 386 if err != nil { 387 return errors.Trace(err) 388 } 389 if ep, err := rel.Endpoint(); err != nil { 390 return errors.Trace(err) 391 } else if !ep.ImplementedBy(ch) { 392 logger.Warningf("skipping relation with unknown endpoint %q", ep.Name) 393 continue 394 } 395 dir, err := ReadStateDir(r.relationsDir, id) 396 if err != nil { 397 return errors.Trace(err) 398 } 399 addErr := r.add(rel, dir) 400 if addErr == nil { 401 continue 402 } 403 removeErr := dir.Remove() 404 if !params.IsCodeCannotEnterScope(addErr) { 405 return errors.Trace(addErr) 406 } 407 if removeErr != nil { 408 return errors.Trace(removeErr) 409 } 410 } 411 if ok, err := r.unit.IsPrincipal(); err != nil { 412 return errors.Trace(err) 413 } else if ok { 414 return nil 415 } 416 // If no Alive relations remain between a subordinate unit's service 417 // and its principal's service, the subordinate must become Dying. 418 for _, relationer := range r.relationers { 419 scope := relationer.ru.Endpoint().Scope 420 if scope == corecharm.ScopeContainer && !relationer.dying { 421 return nil 422 } 423 } 424 return r.unit.Destroy() 425 } 426 427 // add causes the unit agent to join the supplied relation, and to 428 // store persistent state in the supplied dir. 429 func (r *relations) add(rel *uniter.Relation, dir *StateDir) (err error) { 430 logger.Infof("joining relation %q", rel) 431 ru, err := rel.Unit(r.unit) 432 if err != nil { 433 return errors.Trace(err) 434 } 435 relationer := NewRelationer(ru, dir) 436 w, err := r.unit.Watch() 437 if err != nil { 438 return errors.Trace(err) 439 } 440 defer func() { 441 if e := w.Stop(); e != nil { 442 if err == nil { 443 err = e 444 } else { 445 logger.Errorf("error stopping unit watcher: %v", e) 446 } 447 } 448 }() 449 for { 450 select { 451 case <-r.abort: 452 return tomb.ErrDying 453 case _, ok := <-w.Changes(): 454 if !ok { 455 return watcher.EnsureErr(w) 456 } 457 err := relationer.Join() 458 if params.IsCodeCannotEnterScopeYet(err) { 459 logger.Infof("cannot enter scope for relation %q; waiting for subordinate to be removed", rel) 460 continue 461 } else if err != nil { 462 return errors.Trace(err) 463 } 464 logger.Infof("joined relation %q", rel) 465 r.relationers[rel.Id()] = relationer 466 return nil 467 } 468 } 469 } 470 471 // setDying notifies the relationer identified by the supplied id that the 472 // only hook executions to be requested should be those necessary to cleanly 473 // exit the relation. 474 func (r *relations) setDying(id int) error { 475 relationer, found := r.relationers[id] 476 if !found { 477 return nil 478 } 479 if err := relationer.SetDying(); err != nil { 480 return errors.Trace(err) 481 } 482 if relationer.IsImplicit() { 483 delete(r.relationers, id) 484 } 485 return nil 486 }