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