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