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  }