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  }