github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/relation/relationer.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 "fmt" 8 9 "github.com/juju/charm/v12/hooks" 10 "github.com/juju/errors" 11 "github.com/juju/worker/v3/dependency" 12 13 "github.com/juju/juju/rpc/params" 14 "github.com/juju/juju/worker/uniter/hook" 15 "github.com/juju/juju/worker/uniter/runner/context" 16 ) 17 18 // relationer manages a unit's presence in a relation. 19 type relationer struct { 20 relationId int 21 ru RelationUnit 22 stateMgr StateManager 23 unitGetter UnitGetter 24 dying bool 25 26 logger Logger 27 } 28 29 // NewRelationer creates a new relationer. The unit will not join the 30 // relation until explicitly requested. 31 func NewRelationer(ru RelationUnit, stateMgr StateManager, unitGetter UnitGetter, logger Logger) Relationer { 32 return &relationer{ 33 relationId: ru.Relation().Id(), 34 ru: ru, 35 stateMgr: stateMgr, 36 unitGetter: unitGetter, 37 logger: logger, 38 } 39 } 40 41 // ContextInfo returns a representation of the relationer's current state. 42 func (r *relationer) ContextInfo() *context.RelationInfo { 43 st, err := r.stateMgr.Relation(r.relationId) 44 if errors.IsNotFound(err) { 45 st = NewState(r.relationId) 46 } 47 members := st.Members 48 memberNames := make([]string, 0, len(members)) 49 for memberName := range members { 50 memberNames = append(memberNames, memberName) 51 } 52 sh, _ := r.ru.(*RelationUnitShim) 53 return &context.RelationInfo{ 54 RelationUnit: &context.RelationUnitShim{sh.RelationUnit}, 55 MemberNames: memberNames, 56 } 57 } 58 59 // IsImplicit returns whether the local relation endpoint is implicit. Implicit 60 // relations do not run hooks. 61 func (r *relationer) IsImplicit() bool { 62 return r.ru.Endpoint().IsImplicit() 63 } 64 65 // IsDying returns whether the relation is dying. 66 func (r *relationer) IsDying() bool { 67 return r.dying 68 } 69 70 // RelationUnit returns the relation unit associated with this relationer instance. 71 func (r *relationer) RelationUnit() RelationUnit { 72 return r.ru 73 } 74 75 // Join initializes local state and causes the unit to enter its relation 76 // scope, allowing its counterpart units to detect its presence and settings 77 // changes. 78 func (r *relationer) Join() error { 79 if r.dying { 80 return errors.New("dying relationer must not join!") 81 } 82 // We need to make sure the state is persisted inState before we join 83 // the relation, lest a subsequent restart of the unit agent report 84 // local state that doesn't include relations recorded in remote state. 85 if !r.stateMgr.RelationFound(r.relationId) { 86 // Add a state for the new relation to the state manager. 87 st := NewState(r.relationId) 88 if err := r.stateMgr.SetRelation(st); err != nil { 89 return err 90 } 91 } 92 // uniter.RelationUnit.EnterScope() sets the unit's private address 93 // internally automatically, so no need to set it here. 94 return r.ru.EnterScope() 95 } 96 97 // SetDying informs the relationer that the unit is departing the relation, 98 // and that the only hooks it should send henceforth are -departed hooks, 99 // until the relation is empty, followed by a -broken hook. 100 func (r *relationer) SetDying() error { 101 if r.IsImplicit() { 102 r.dying = true 103 return r.die() 104 } 105 r.dying = true 106 return nil 107 } 108 109 // die is run when the relationer has no further responsibilities; it leaves 110 // relation scope, and removes relation state. 111 func (r *relationer) die() error { 112 err := r.ru.LeaveScope() 113 if err != nil && !params.IsCodeNotFoundOrCodeUnauthorized(err) { 114 return errors.Annotatef(err, "leaving scope of relation %q", r.ru.Relation()) 115 } 116 return r.stateMgr.RemoveRelation(r.relationId, r.unitGetter, map[string]bool{}) 117 } 118 119 // PrepareHook checks that the relation is in a state such that it makes 120 // sense to execute the supplied hook, and ensures that the relation context 121 // contains the latest relation state as communicated in the hook.Info. It 122 // returns the name of the hook that must be run. 123 func (r *relationer) PrepareHook(hi hook.Info) (string, error) { 124 if r.IsImplicit() { 125 // Implicit relations always return ErrNoOperation from 126 // NextOp. Something broken if we reach here. 127 r.logger.Errorf("implicit relations must not run hooks") 128 return "", dependency.ErrBounce 129 } 130 st, err := r.stateMgr.Relation(hi.RelationId) 131 if err != nil { 132 return "", errors.Trace(err) 133 } 134 if err = st.Validate(hi); err != nil { 135 return "", errors.Trace(err) 136 } 137 name := r.ru.Endpoint().Name 138 return fmt.Sprintf("%s-%s", name, hi.Kind), nil 139 } 140 141 // CommitHook persists the fact of the supplied hook's completion. 142 func (r *relationer) CommitHook(hi hook.Info) error { 143 if r.IsImplicit() { 144 // Implicit relations always return ErrNoOperation from 145 // NextOp. Something broken if we reach here. 146 r.logger.Errorf("implicit relations must not run hooks") 147 return dependency.ErrBounce 148 } 149 if hi.Kind == hooks.RelationBroken { 150 return r.die() 151 } 152 st, err := r.stateMgr.Relation(hi.RelationId) 153 if err != nil { 154 return errors.Trace(err) 155 } 156 st.UpdateStateForHook(hi, r.logger) 157 return r.stateMgr.SetRelation(st) 158 }