github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/relation/statetracker.go (about) 1 // Copyright 2012-2020 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package relation 5 6 import ( 7 "os" 8 "strconv" 9 "time" 10 11 "github.com/juju/charm/v12" 12 "github.com/juju/charm/v12/hooks" 13 "github.com/juju/errors" 14 "github.com/juju/names/v5" 15 "github.com/juju/worker/v3" 16 "github.com/kr/pretty" 17 18 "github.com/juju/juju/api/agent/uniter" 19 "github.com/juju/juju/core/leadership" 20 "github.com/juju/juju/core/life" 21 "github.com/juju/juju/core/relation" 22 "github.com/juju/juju/rpc/params" 23 "github.com/juju/juju/worker/uniter/hook" 24 "github.com/juju/juju/worker/uniter/operation" 25 "github.com/juju/juju/worker/uniter/remotestate" 26 "github.com/juju/juju/worker/uniter/resolver" 27 "github.com/juju/juju/worker/uniter/runner/context" 28 ) 29 30 // LeadershipContextFunc is a function that returns a leadership context. 31 type LeadershipContextFunc func(accessor context.LeadershipSettingsAccessor, tracker leadership.Tracker, unitName string) context.LeadershipContext 32 33 // RelationStateTrackerConfig contains configuration values for creating a new 34 // RelationStateTracker instance. 35 type RelationStateTrackerConfig struct { 36 State *uniter.State 37 Unit *uniter.Unit 38 Tracker leadership.Tracker 39 CharmDir string 40 NewLeadershipContext LeadershipContextFunc 41 Abort <-chan struct{} 42 Logger Logger 43 } 44 45 // relationStateTracker implements RelationStateTracker. 46 type relationStateTracker struct { 47 st StateTrackerState 48 unit Unit 49 leaderCtx context.LeadershipContext 50 abort <-chan struct{} 51 subordinate bool 52 principalName string 53 charmDir string 54 relationers map[int]Relationer 55 remoteAppName map[int]string 56 relationCreated map[int]bool 57 isPeerRelation map[int]bool 58 stateMgr StateManager 59 logger Logger 60 newRelationer func(RelationUnit, StateManager, UnitGetter, Logger) Relationer 61 } 62 63 // NewRelationStateTracker returns a new RelationStateTracker instance. 64 func NewRelationStateTracker(cfg RelationStateTrackerConfig) (RelationStateTracker, error) { 65 principalName, subordinate, err := cfg.Unit.PrincipalName() 66 if err != nil { 67 return nil, errors.Trace(err) 68 } 69 leadershipContext := cfg.NewLeadershipContext( 70 cfg.State.LeadershipSettings, 71 cfg.Tracker, 72 cfg.Unit.Tag().Id(), 73 ) 74 75 r := &relationStateTracker{ 76 st: &stateTrackerStateShim{cfg.State}, 77 unit: &unitShim{cfg.Unit}, 78 leaderCtx: leadershipContext, 79 subordinate: subordinate, 80 principalName: principalName, 81 charmDir: cfg.CharmDir, 82 relationers: make(map[int]Relationer), 83 remoteAppName: make(map[int]string), 84 relationCreated: make(map[int]bool), 85 isPeerRelation: make(map[int]bool), 86 abort: cfg.Abort, 87 logger: cfg.Logger, 88 newRelationer: NewRelationer, 89 } 90 r.stateMgr, err = NewStateManager(r.unit, r.logger) 91 if err != nil { 92 return nil, errors.Trace(err) 93 } 94 if err := r.loadInitialState(); err != nil { 95 return nil, errors.Trace(err) 96 } 97 return r, nil 98 } 99 100 // loadInitialState reconciles the local state with the remote 101 // state of the corresponding relations. 102 func (r *relationStateTracker) loadInitialState() error { 103 relationStatus, err := r.unit.RelationsStatus() 104 if err != nil { 105 return errors.Trace(err) 106 } 107 108 // Keep the relations ordered for reliable testing. 109 var orderedIds []int 110 isScopeRelations := make(map[int]Relation) 111 relationSuspended := make(map[int]bool) 112 for _, rs := range relationStatus { 113 if !rs.InScope { 114 continue 115 } 116 rel, err := r.st.Relation(rs.Tag) 117 if err != nil { 118 return errors.Trace(err) 119 } 120 relationSuspended[rel.Id()] = rs.Suspended 121 isScopeRelations[rel.Id()] = rel 122 orderedIds = append(orderedIds, rel.Id()) 123 124 // The relation-created hook always fires before joining. 125 // Since we are already in scope, the relation-created hook 126 // must have fired in the past so we can mark the relation as 127 // already created. 128 r.relationCreated[rel.Id()] = true 129 } 130 131 if r.logger.IsTraceEnabled() { 132 r.logger.Tracef("initialising relation state tracker: %# v", pretty.Formatter(r.stateMgr.(*stateManager).relationState)) 133 } 134 knownUnits := make(map[string]bool) 135 for _, id := range r.stateMgr.KnownIDs() { 136 if rel, ok := isScopeRelations[id]; ok { 137 //shouldJoin := localRelState.Members[rel.] 138 if err := r.joinRelation(rel); err != nil { 139 return errors.Trace(err) 140 } 141 } else if !relationSuspended[id] { 142 // Relations which are suspended may become active 143 // again so we keep the local state, otherwise we 144 // remove it. 145 if err := r.stateMgr.RemoveRelation(id, r.st, knownUnits); err != nil { 146 return errors.Trace(err) 147 } 148 } 149 } 150 151 for _, id := range orderedIds { 152 rel := isScopeRelations[id] 153 if r.stateMgr.RelationFound(id) { 154 continue 155 } 156 if err := r.joinRelation(rel); err != nil { 157 return errors.Trace(err) 158 } 159 } 160 return nil 161 } 162 163 func (r *relationStateTracker) relationGone(id int) { 164 delete(r.relationers, id) 165 delete(r.remoteAppName, id) 166 delete(r.isPeerRelation, id) 167 delete(r.relationCreated, id) 168 } 169 170 // joinRelation causes the unit agent to join the supplied relation, and to 171 // store persistent state. It will block until the 172 // operation succeeds or fails; or until the abort chan is closed, in which 173 // case it will return resolver.ErrLoopAborted. 174 func (r *relationStateTracker) joinRelation(rel Relation) (err error) { 175 unitName := r.unit.Name() 176 r.logger.Tracef("%q (re-)joining: %q", unitName, rel) 177 ru, err := rel.Unit(r.unit.Tag()) 178 if err != nil { 179 return errors.Trace(err) 180 } 181 relationer := r.newRelationer(ru, r.stateMgr, r.st, r.logger) 182 unitWatcher, err := r.unit.Watch() 183 if err != nil { 184 return errors.Trace(err) 185 } 186 defer func() { 187 if e := worker.Stop(unitWatcher); e != nil { 188 if err == nil { 189 err = e 190 } else { 191 r.logger.Errorf("while stopping unit watcher: %v", e) 192 } 193 } 194 }() 195 timeout := time.After(time.Minute) 196 for { 197 select { 198 case <-r.abort: 199 // Should this be a different error? e.g. resolver.ErrAborted, that 200 // Loop translates into ErrLoopAborted? 201 return resolver.ErrLoopAborted 202 case <-timeout: 203 return errors.Errorf("unit watcher for %q failed to trigger joining relation %q", unitName, rel) 204 case _, ok := <-unitWatcher.Changes(): 205 if !ok { 206 return errors.New("unit watcher closed") 207 } 208 err := relationer.Join() 209 if params.IsCodeCannotEnterScopeYet(err) { 210 r.logger.Infof("cannot enter scope for relation %q; waiting for subordinate to be removed", rel) 211 continue 212 } else if err != nil { 213 return errors.Trace(err) 214 } 215 // Leaders get to set the relation status. 216 var isLeader bool 217 isLeader, err = r.leaderCtx.IsLeader() 218 if err != nil { 219 return errors.Trace(err) 220 } 221 r.logger.Debugf("unit %q (leader=%v) entered scope for relation %q", unitName, isLeader, rel) 222 if isLeader { 223 err = rel.SetStatus(relation.Joined) 224 if err != nil { 225 return errors.Trace(err) 226 } 227 } 228 r.relationers[rel.Id()] = relationer 229 return nil 230 } 231 } 232 } 233 234 func (r *relationStateTracker) SynchronizeScopes(remote remotestate.Snapshot) error { 235 if r.logger.IsTraceEnabled() { 236 r.logger.Tracef("%q synchronise scopes for remote relations %# v", r.unit.Name(), pretty.Formatter(remote.Relations)) 237 } 238 var charmSpec *charm.CharmDir 239 knownUnits := make(map[string]bool) 240 for id, relationSnapshot := range remote.Relations { 241 if relr, found := r.relationers[id]; found { 242 // We've seen this relation before. The only changes 243 // we care about are to the lifecycle state or status, 244 // and to the member settings versions. We handle 245 // differences in settings in nextRelationHook. 246 relr.RelationUnit().Relation().UpdateSuspended(relationSnapshot.Suspended) 247 if relationSnapshot.Life == life.Dying || relationSnapshot.Suspended { 248 if err := r.setDying(id); err != nil { 249 return errors.Trace(err) 250 } 251 } 252 r.logger.Tracef("already seen relation id %v", id) 253 continue 254 } 255 256 // Relations that are not alive are simply skipped, because they 257 // were not previously known anyway. 258 if relationSnapshot.Life != life.Alive || relationSnapshot.Suspended { 259 continue 260 } 261 rel, err := r.st.RelationById(id) 262 if err != nil { 263 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 264 r.relationGone(id) 265 r.logger.Tracef("relation id %v has been removed", id) 266 continue 267 } 268 return errors.Trace(err) 269 } 270 271 ep, err := rel.Endpoint() 272 if err != nil { 273 return errors.Trace(err) 274 } 275 // Keep track of peer relations 276 if ep.Role == charm.RolePeer { 277 r.isPeerRelation[id] = true 278 } 279 // Keep track of the remote application 280 r.remoteAppName[id] = rel.OtherApplication() 281 282 // Make sure we ignore relations not implemented by the unit's charm. 283 if !r.RelationCreated(id) { 284 if charmSpec == nil { 285 charmSpec, err = charm.ReadCharmDir(r.charmDir) 286 if err != nil { 287 if !os.IsNotExist(errors.Cause(err)) { 288 return errors.Trace(err) 289 } 290 r.logger.Warningf("charm deleted, skipping relation endpoint check for %q", rel) 291 } 292 } 293 if charmSpec != nil && !ep.ImplementedBy(charmSpec) { 294 r.logger.Warningf("skipping relation %q with unknown endpoint %q", rel, ep.Name) 295 continue 296 } 297 } 298 299 if joinErr := r.joinRelation(rel); joinErr != nil { 300 removeErr := r.stateMgr.RemoveRelation(id, r.st, knownUnits) 301 if !params.IsCodeCannotEnterScope(joinErr) { 302 return errors.Trace(joinErr) 303 } else if errors.IsNotFound(joinErr) { 304 continue 305 } else if removeErr != nil { 306 return errors.Trace(removeErr) 307 } 308 } 309 } 310 311 if r.subordinate { 312 return r.maybeSetSubordinateDying() 313 } 314 315 return nil 316 } 317 318 func (r *relationStateTracker) maybeSetSubordinateDying() error { 319 // If no Alive relations remain between a subordinate unit's application 320 // and its principal's application, the subordinate must become Dying. 321 principalApp, err := names.UnitApplication(r.principalName) 322 if err != nil { 323 return errors.Trace(err) 324 } 325 for _, relationer := range r.relationers { 326 relUnit := relationer.RelationUnit() 327 if relUnit.Relation().OtherApplication() != principalApp { 328 continue 329 } 330 scope := relUnit.Endpoint().Scope 331 if scope == charm.ScopeContainer && !relationer.IsDying() { 332 return nil 333 } 334 } 335 return r.unit.Destroy() 336 } 337 338 // setDying notifies the relationer identified by the supplied id that the 339 // only hook executions to be requested should be those necessary to cleanly 340 // exit the relation. 341 func (r *relationStateTracker) setDying(id int) error { 342 relationer, found := r.relationers[id] 343 if !found { 344 return nil 345 } 346 if err := relationer.SetDying(); err != nil { 347 return errors.Trace(err) 348 } 349 if relationer.IsImplicit() { 350 delete(r.relationers, id) 351 } 352 return nil 353 } 354 355 // IsKnown returns true if the relation ID is known by the tracker. 356 func (r *relationStateTracker) IsKnown(id int) bool { 357 return r.relationers[id] != nil 358 } 359 360 // IsImplicit returns true if the endpoint for a relation ID is implicit. 361 func (r *relationStateTracker) IsImplicit(id int) (bool, error) { 362 if rel := r.relationers[id]; rel != nil { 363 return rel.IsImplicit(), nil 364 } 365 return false, errors.NotFoundf("relation: %d", id) 366 } 367 368 // IsPeerRelation returns true if the endpoint for a relation ID has a Peer role. 369 func (r *relationStateTracker) IsPeerRelation(id int) (bool, error) { 370 if rel := r.relationers[id]; rel != nil { 371 return r.isPeerRelation[id], nil 372 } 373 374 return false, errors.NotFoundf("relation: %d", id) 375 } 376 377 // HasContainerScope returns true if the specified relation ID has a container 378 // scope. 379 func (r *relationStateTracker) HasContainerScope(id int) (bool, error) { 380 if rel := r.relationers[id]; rel != nil { 381 return rel.RelationUnit().Endpoint().Scope == charm.ScopeContainer, nil 382 } 383 384 return false, errors.NotFoundf("relation: %d", id) 385 } 386 387 // RelationCreated returns true if a relation created hook has been 388 // fired for the specified relation ID. 389 func (r *relationStateTracker) RelationCreated(id int) bool { 390 return r.relationCreated[id] 391 } 392 393 // RemoteApplication returns the remote application name associated with the 394 // specified relation ID. 395 func (r *relationStateTracker) RemoteApplication(id int) string { 396 return r.remoteAppName[id] 397 } 398 399 // State returns a State instance for accessing the persisted state for a 400 // relation ID. 401 func (r *relationStateTracker) State(id int) (*State, error) { 402 if rel, ok := r.relationers[id]; ok && rel != nil { 403 return r.stateMgr.Relation(id) 404 } 405 406 return nil, errors.NotFoundf("relation: %d", id) 407 } 408 409 func (r *relationStateTracker) StateFound(id int) bool { 410 return r.stateMgr.RelationFound(id) 411 } 412 413 // PrepareHook is part of the RelationStateTracker interface. 414 func (r *relationStateTracker) PrepareHook(hookInfo hook.Info) (string, error) { 415 if !hookInfo.Kind.IsRelation() { 416 return "", errors.Errorf("not a relation hook: %#v", hookInfo) 417 } 418 relationer, found := r.relationers[hookInfo.RelationId] 419 if !found { 420 // There may have been a hook queued prior to a restart 421 // and the relation has since been deleted. 422 // There's nothing to prepare so allow the uniter 423 // to continue with the next operation. 424 r.logger.Warningf("preparing hook %v for %v, relation %d has been removed", hookInfo.Kind, r.principalName, hookInfo.RelationId) 425 return "", operation.ErrSkipExecute 426 } 427 return relationer.PrepareHook(hookInfo) 428 } 429 430 // CommitHook is part of the RelationStateTracker interface. 431 func (r *relationStateTracker) CommitHook(hookInfo hook.Info) (err error) { 432 defer func() { 433 if err != nil { 434 return 435 } 436 437 if hookInfo.Kind == hooks.RelationCreated { 438 r.relationCreated[hookInfo.RelationId] = true 439 } else if hookInfo.Kind == hooks.RelationBroken { 440 r.relationGone(hookInfo.RelationId) 441 } 442 }() 443 if !hookInfo.Kind.IsRelation() { 444 return errors.Errorf("not a relation hook: %#v", hookInfo) 445 } 446 relationer, found := r.relationers[hookInfo.RelationId] 447 if !found { 448 // There may have been a hook queued prior to a restart 449 // and the relation has since been deleted. 450 // There's nothing to commit so allow the uniter 451 // to continue with the next operation. 452 r.logger.Warningf("committing hook %v for %v, relation %d has been removed", hookInfo.Kind, r.principalName, hookInfo.RelationId) 453 return nil 454 } 455 return relationer.CommitHook(hookInfo) 456 } 457 458 // GetInfo is part of the Relations interface. 459 func (r *relationStateTracker) GetInfo() map[int]*context.RelationInfo { 460 relationInfos := map[int]*context.RelationInfo{} 461 for id, relationer := range r.relationers { 462 relationInfos[id] = relationer.ContextInfo() 463 } 464 return relationInfos 465 } 466 467 // Name is part of the Relations interface. 468 func (r *relationStateTracker) Name(id int) (string, error) { 469 relationer, found := r.relationers[id] 470 if !found { 471 return "", errors.NotFoundf("relation: %d", id) 472 } 473 return relationer.RelationUnit().Endpoint().Name, nil 474 } 475 476 // LocalUnitName returns the name for the local unit. 477 func (r *relationStateTracker) LocalUnitName() string { 478 return r.unit.Name() 479 } 480 481 // LocalUnitAndApplicationLife returns the life values for the local unit and 482 // application. 483 func (r *relationStateTracker) LocalUnitAndApplicationLife() (life.Value, life.Value, error) { 484 if err := r.unit.Refresh(); err != nil { 485 return life.Value(""), life.Value(""), errors.Trace(err) 486 } 487 488 app, err := r.unit.Application() 489 if err != nil { 490 return life.Value(""), life.Value(""), errors.Trace(err) 491 } 492 493 return r.unit.Life(), app.Life(), nil 494 } 495 496 // Report provides information for the engine report. 497 func (r *relationStateTracker) Report() map[string]interface{} { 498 result := make(map[string]interface{}) 499 500 stateMgr, ok := r.stateMgr.(*stateManager) 501 if !ok { 502 return nil 503 } 504 stateMgr.mu.Lock() 505 relationState := stateMgr.relationState 506 stateMgr.mu.Unlock() 507 508 for id, st := range relationState { 509 report := map[string]interface{}{ 510 "application-members": st.ApplicationMembers, 511 "members": st.Members, 512 "is-peer": r.isPeerRelation[id], 513 } 514 515 // Ensure that the relationer exists and is alive before reporting 516 // the information. 517 if relationer, ok := r.relationers[id]; ok && relationer != nil { 518 report["dying"] = relationer.IsDying() 519 report["endpoint"] = relationer.RelationUnit().Endpoint().Name 520 report["relation"] = relationer.RelationUnit().Relation().String() 521 } 522 523 result[strconv.Itoa(id)] = report 524 } 525 526 return result 527 }