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  }