github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/errors"
     8  	"github.com/juju/loggo"
     9  	"github.com/juju/names"
    10  	"github.com/juju/utils/set"
    11  	corecharm "gopkg.in/juju/charm.v6-unstable"
    12  	"gopkg.in/juju/charm.v6-unstable/hooks"
    13  
    14  	"github.com/juju/juju/api/uniter"
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/worker"
    17  	"github.com/juju/juju/worker/uniter/hook"
    18  	"github.com/juju/juju/worker/uniter/operation"
    19  	"github.com/juju/juju/worker/uniter/remotestate"
    20  	"github.com/juju/juju/worker/uniter/resolver"
    21  	"github.com/juju/juju/worker/uniter/runner/context"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.worker.uniter.relation")
    25  
    26  // Relations exists to encapsulate relation state and operations behind an
    27  // interface for the benefit of future refactoring.
    28  type Relations interface {
    29  	// Name returns the name of the relation with the supplied id, or an error
    30  	// if the relation is unknown.
    31  	Name(id int) (string, error)
    32  
    33  	// PrepareHook returns the name of the supplied relation hook, or an error
    34  	// if the hook is unknown or invalid given current state.
    35  	PrepareHook(hookInfo hook.Info) (string, error)
    36  
    37  	// CommitHook persists the state change encoded in the supplied relation
    38  	// hook, or returns an error if the hook is unknown or invalid given
    39  	// current relation state.
    40  	CommitHook(hookInfo hook.Info) error
    41  
    42  	// GetInfo returns information about current relation state.
    43  	GetInfo() map[int]*context.RelationInfo
    44  
    45  	// NextHook returns details on the next hook to execute, based on the local
    46  	// and remote states.
    47  	NextHook(resolver.LocalState, remotestate.Snapshot) (hook.Info, error)
    48  }
    49  
    50  // NewRelationsResolver returns a new Resolver that handles differences in
    51  // relation state.
    52  func NewRelationsResolver(r Relations) resolver.Resolver {
    53  	return &relationsResolver{r}
    54  }
    55  
    56  type relationsResolver struct {
    57  	relations Relations
    58  }
    59  
    60  // NextOp implements resolver.Resolver.
    61  func (s *relationsResolver) NextOp(
    62  	localState resolver.LocalState,
    63  	remoteState remotestate.Snapshot,
    64  	opFactory operation.Factory,
    65  ) (operation.Operation, error) {
    66  	hook, err := s.relations.NextHook(localState, remoteState)
    67  	if err != nil {
    68  		return nil, errors.Trace(err)
    69  	}
    70  	return opFactory.NewRunHook(hook)
    71  }
    72  
    73  // relations implements Relations.
    74  type relations struct {
    75  	st           *uniter.State
    76  	unit         *uniter.Unit
    77  	charmDir     string
    78  	relationsDir string
    79  	relationers  map[int]*Relationer
    80  	abort        <-chan struct{}
    81  }
    82  
    83  // NewRelations returns a new Relations instance.
    84  func NewRelations(st *uniter.State, tag names.UnitTag, charmDir, relationsDir string, abort <-chan struct{}) (Relations, error) {
    85  	unit, err := st.Unit(tag)
    86  	if err != nil {
    87  		return nil, errors.Trace(err)
    88  	}
    89  	r := &relations{
    90  		st:           st,
    91  		unit:         unit,
    92  		charmDir:     charmDir,
    93  		relationsDir: relationsDir,
    94  		relationers:  make(map[int]*Relationer),
    95  		abort:        abort,
    96  	}
    97  	if err := r.init(); err != nil {
    98  		return nil, errors.Trace(err)
    99  	}
   100  	return r, nil
   101  }
   102  
   103  // init reconciles the local relation state dirs with the remote state of
   104  // the corresponding relations. It's only expected to be called while a
   105  // *relations is being created.
   106  func (r *relations) init() error {
   107  	joinedRelationTags, err := r.unit.JoinedRelations()
   108  	if err != nil {
   109  		return errors.Trace(err)
   110  	}
   111  	joinedRelations := make(map[int]*uniter.Relation)
   112  	for _, tag := range joinedRelationTags {
   113  		relation, err := r.st.Relation(tag)
   114  		if err != nil {
   115  			return errors.Trace(err)
   116  		}
   117  		joinedRelations[relation.Id()] = relation
   118  	}
   119  	knownDirs, err := ReadAllStateDirs(r.relationsDir)
   120  	if err != nil {
   121  		return errors.Trace(err)
   122  	}
   123  	for id, dir := range knownDirs {
   124  		if rel, ok := joinedRelations[id]; ok {
   125  			if err := r.add(rel, dir); err != nil {
   126  				return errors.Trace(err)
   127  			}
   128  		} else if err := dir.Remove(); err != nil {
   129  			return errors.Trace(err)
   130  		}
   131  	}
   132  	for id, rel := range joinedRelations {
   133  		if _, ok := knownDirs[id]; ok {
   134  			continue
   135  		}
   136  		dir, err := ReadStateDir(r.relationsDir, id)
   137  		if err != nil {
   138  			return errors.Trace(err)
   139  		}
   140  		if err := r.add(rel, dir); err != nil {
   141  			return errors.Trace(err)
   142  		}
   143  	}
   144  	return nil
   145  }
   146  
   147  // NextHook implements Relations.
   148  func (r *relations) NextHook(
   149  	localState resolver.LocalState,
   150  	remoteState remotestate.Snapshot,
   151  ) (hook.Info, error) {
   152  
   153  	if remoteState.Life == params.Dying {
   154  		// The unit is Dying, so make sure all subordinates are dying.
   155  		var destroyAllSubordinates bool
   156  		for relationId, relationSnapshot := range remoteState.Relations {
   157  			if relationSnapshot.Life != params.Alive {
   158  				continue
   159  			}
   160  			relationer, ok := r.relationers[relationId]
   161  			if !ok {
   162  				continue
   163  			}
   164  			if relationer.ru.Endpoint().Scope == corecharm.ScopeContainer {
   165  				relationSnapshot.Life = params.Dying
   166  				remoteState.Relations[relationId] = relationSnapshot
   167  				destroyAllSubordinates = true
   168  			}
   169  		}
   170  		if destroyAllSubordinates {
   171  			if err := r.unit.DestroyAllSubordinates(); err != nil {
   172  				return hook.Info{}, errors.Trace(err)
   173  			}
   174  		}
   175  	}
   176  
   177  	// Add/remove local relation state; enter and leave scope as necessary.
   178  	if err := r.update(remoteState.Relations); err != nil {
   179  		return hook.Info{}, errors.Trace(err)
   180  	}
   181  
   182  	if localState.Kind != operation.Continue {
   183  		return hook.Info{}, resolver.ErrNoOperation
   184  	}
   185  
   186  	// See if any of the relations have operations to perform.
   187  	for relationId, relationSnapshot := range remoteState.Relations {
   188  		relationer, ok := r.relationers[relationId]
   189  		if !ok || relationer.IsImplicit() {
   190  			continue
   191  		}
   192  		var remoteBroken bool
   193  		if remoteState.Life == params.Dying || relationSnapshot.Life == params.Dying {
   194  			relationSnapshot = remotestate.RelationSnapshot{}
   195  			remoteBroken = true
   196  			// TODO(axw) if relation is implicit, leave scope & remove.
   197  		}
   198  		// If either the unit or the relation are Dying,
   199  		// then the relation should be broken.
   200  		hook, err := nextRelationHook(relationer.dir.State(), relationSnapshot, remoteBroken)
   201  		if err == resolver.ErrNoOperation {
   202  			continue
   203  		}
   204  		return hook, err
   205  	}
   206  	return hook.Info{}, resolver.ErrNoOperation
   207  }
   208  
   209  // nextRelationHook returns the next hook op that should be executed in the
   210  // relation characterised by the supplied local and remote state; or an error
   211  // if the states do not refer to the same relation; or ErrRelationUpToDate if
   212  // no hooks need to be executed.
   213  func nextRelationHook(
   214  	local *State,
   215  	remote remotestate.RelationSnapshot,
   216  	remoteBroken bool,
   217  ) (hook.Info, error) {
   218  
   219  	// If there's a guaranteed next hook, return that.
   220  	relationId := local.RelationId
   221  	if local.ChangedPending != "" {
   222  		unitName := local.ChangedPending
   223  		return hook.Info{
   224  			Kind:          hooks.RelationChanged,
   225  			RelationId:    relationId,
   226  			RemoteUnit:    unitName,
   227  			ChangeVersion: remote.Members[unitName],
   228  		}, nil
   229  	}
   230  
   231  	// Get the union of all relevant units, and sort them, so we produce events
   232  	// in a consistent order (largely for the convenience of the tests).
   233  	allUnitNames := set.NewStrings()
   234  	for unitName := range local.Members {
   235  		allUnitNames.Add(unitName)
   236  	}
   237  	for unitName := range remote.Members {
   238  		allUnitNames.Add(unitName)
   239  	}
   240  	sortedUnitNames := allUnitNames.SortedValues()
   241  
   242  	// If there are any locally known units that are no longer reflected in
   243  	// remote state, depart them.
   244  	for _, unitName := range sortedUnitNames {
   245  		changeVersion, found := local.Members[unitName]
   246  		if !found {
   247  			continue
   248  		}
   249  		if _, found := remote.Members[unitName]; !found {
   250  			return hook.Info{
   251  				Kind:          hooks.RelationDeparted,
   252  				RelationId:    relationId,
   253  				RemoteUnit:    unitName,
   254  				ChangeVersion: changeVersion,
   255  			}, nil
   256  		}
   257  	}
   258  
   259  	// If the relation's meant to be broken, break it.
   260  	if remoteBroken {
   261  		return hook.Info{
   262  			Kind:       hooks.RelationBroken,
   263  			RelationId: relationId,
   264  		}, nil
   265  	}
   266  
   267  	// If there are any remote units not locally known, join them.
   268  	for _, unitName := range sortedUnitNames {
   269  		changeVersion, found := remote.Members[unitName]
   270  		if !found {
   271  			continue
   272  		}
   273  		if _, found := local.Members[unitName]; !found {
   274  			return hook.Info{
   275  				Kind:          hooks.RelationJoined,
   276  				RelationId:    relationId,
   277  				RemoteUnit:    unitName,
   278  				ChangeVersion: changeVersion,
   279  			}, nil
   280  		}
   281  	}
   282  
   283  	// Finally scan for remote units whose latest version is not reflected
   284  	// in local state.
   285  	for _, unitName := range sortedUnitNames {
   286  		remoteChangeVersion, found := remote.Members[unitName]
   287  		if !found {
   288  			continue
   289  		}
   290  		localChangeVersion, found := local.Members[unitName]
   291  		if !found {
   292  			continue
   293  		}
   294  		// NOTE(axw) we use != and not > to cater due to the
   295  		// use of the relation settings document's txn-revno
   296  		// as the version. When model-uuid migration occurs, the
   297  		// document is recreated, resetting txn-revno.
   298  		if remoteChangeVersion != localChangeVersion {
   299  			return hook.Info{
   300  				Kind:          hooks.RelationChanged,
   301  				RelationId:    relationId,
   302  				RemoteUnit:    unitName,
   303  				ChangeVersion: remoteChangeVersion,
   304  			}, nil
   305  		}
   306  	}
   307  
   308  	// Nothing left to do for this relation.
   309  	return hook.Info{}, resolver.ErrNoOperation
   310  }
   311  
   312  // Name is part of the Relations interface.
   313  func (r *relations) Name(id int) (string, error) {
   314  	relationer, found := r.relationers[id]
   315  	if !found {
   316  		return "", errors.Errorf("unknown relation: %d", id)
   317  	}
   318  	return relationer.ru.Endpoint().Name, nil
   319  }
   320  
   321  // PrepareHook is part of the Relations interface.
   322  func (r *relations) PrepareHook(hookInfo hook.Info) (string, error) {
   323  	if !hookInfo.Kind.IsRelation() {
   324  		return "", errors.Errorf("not a relation hook: %#v", hookInfo)
   325  	}
   326  	relationer, found := r.relationers[hookInfo.RelationId]
   327  	if !found {
   328  		return "", errors.Errorf("unknown relation: %d", hookInfo.RelationId)
   329  	}
   330  	return relationer.PrepareHook(hookInfo)
   331  }
   332  
   333  // CommitHook is part of the Relations interface.
   334  func (r *relations) CommitHook(hookInfo hook.Info) error {
   335  	if !hookInfo.Kind.IsRelation() {
   336  		return errors.Errorf("not a relation hook: %#v", hookInfo)
   337  	}
   338  	relationer, found := r.relationers[hookInfo.RelationId]
   339  	if !found {
   340  		return errors.Errorf("unknown relation: %d", hookInfo.RelationId)
   341  	}
   342  	if hookInfo.Kind == hooks.RelationBroken {
   343  		delete(r.relationers, hookInfo.RelationId)
   344  	}
   345  	return relationer.CommitHook(hookInfo)
   346  }
   347  
   348  // GetInfo is part of the Relations interface.
   349  func (r *relations) GetInfo() map[int]*context.RelationInfo {
   350  	relationInfos := map[int]*context.RelationInfo{}
   351  	for id, relationer := range r.relationers {
   352  		relationInfos[id] = relationer.ContextInfo()
   353  	}
   354  	return relationInfos
   355  }
   356  
   357  func (r *relations) update(remote map[int]remotestate.RelationSnapshot) error {
   358  	for id, relationSnapshot := range remote {
   359  		if _, found := r.relationers[id]; found {
   360  			// We've seen this relation before. The only changes
   361  			// we care about are to the lifecycle state, and to
   362  			// the member settings versions. We handle differences
   363  			// in settings in nextRelationHook.
   364  			if relationSnapshot.Life == params.Dying {
   365  				if err := r.setDying(id); err != nil {
   366  					return errors.Trace(err)
   367  				}
   368  			}
   369  			continue
   370  		}
   371  		// Relations that are not alive are simply skipped, because they
   372  		// were not previously known anyway.
   373  		if relationSnapshot.Life != params.Alive {
   374  			continue
   375  		}
   376  		rel, err := r.st.RelationById(id)
   377  		if err != nil {
   378  			if params.IsCodeNotFoundOrCodeUnauthorized(err) {
   379  				continue
   380  			}
   381  			return errors.Trace(err)
   382  		}
   383  		// Make sure we ignore relations not implemented by the unit's charm.
   384  		ch, err := corecharm.ReadCharmDir(r.charmDir)
   385  		if err != nil {
   386  			return errors.Trace(err)
   387  		}
   388  		if ep, err := rel.Endpoint(); err != nil {
   389  			return errors.Trace(err)
   390  		} else if !ep.ImplementedBy(ch) {
   391  			logger.Warningf("skipping relation with unknown endpoint %q", ep.Name)
   392  			continue
   393  		}
   394  		dir, err := ReadStateDir(r.relationsDir, id)
   395  		if err != nil {
   396  			return errors.Trace(err)
   397  		}
   398  		addErr := r.add(rel, dir)
   399  		if addErr == nil {
   400  			continue
   401  		}
   402  		removeErr := dir.Remove()
   403  		if !params.IsCodeCannotEnterScope(addErr) {
   404  			return errors.Trace(addErr)
   405  		}
   406  		if removeErr != nil {
   407  			return errors.Trace(removeErr)
   408  		}
   409  	}
   410  	if ok, err := r.unit.IsPrincipal(); err != nil {
   411  		return errors.Trace(err)
   412  	} else if ok {
   413  		return nil
   414  	}
   415  	// If no Alive relations remain between a subordinate unit's service
   416  	// and its principal's service, the subordinate must become Dying.
   417  	for _, relationer := range r.relationers {
   418  		scope := relationer.ru.Endpoint().Scope
   419  		if scope == corecharm.ScopeContainer && !relationer.dying {
   420  			return nil
   421  		}
   422  	}
   423  	return r.unit.Destroy()
   424  }
   425  
   426  // add causes the unit agent to join the supplied relation, and to
   427  // store persistent state in the supplied dir. It will block until the
   428  // operation succeeds or fails; or until the abort chan is closed, in
   429  // which case it will return resolver.ErrLoopAborted.
   430  func (r *relations) add(rel *uniter.Relation, dir *StateDir) (err error) {
   431  	logger.Infof("joining relation %q", rel)
   432  	ru, err := rel.Unit(r.unit)
   433  	if err != nil {
   434  		return errors.Trace(err)
   435  	}
   436  	relationer := NewRelationer(ru, dir)
   437  	unitWatcher, err := r.unit.Watch()
   438  	if err != nil {
   439  		return errors.Trace(err)
   440  	}
   441  	defer func() {
   442  		if e := worker.Stop(unitWatcher); e != nil {
   443  			if err == nil {
   444  				err = e
   445  			} else {
   446  				logger.Errorf("while stopping unit watcher: %v", e)
   447  			}
   448  		}
   449  	}()
   450  	for {
   451  		select {
   452  		case <-r.abort:
   453  			// Should this be a different error? e.g. resolver.ErrAborted, that
   454  			// Loop translates into ErrLoopAborted?
   455  			return resolver.ErrLoopAborted
   456  		case _, ok := <-unitWatcher.Changes():
   457  			if !ok {
   458  				return errors.New("unit watcher closed")
   459  			}
   460  			err := relationer.Join()
   461  			if params.IsCodeCannotEnterScopeYet(err) {
   462  				logger.Infof("cannot enter scope for relation %q; waiting for subordinate to be removed", rel)
   463  				continue
   464  			} else if err != nil {
   465  				return errors.Trace(err)
   466  			}
   467  			logger.Infof("joined relation %q", rel)
   468  			r.relationers[rel.Id()] = relationer
   469  			return nil
   470  		}
   471  	}
   472  }
   473  
   474  // setDying notifies the relationer identified by the supplied id that the
   475  // only hook executions to be requested should be those necessary to cleanly
   476  // exit the relation.
   477  func (r *relations) setDying(id int) error {
   478  	relationer, found := r.relationers[id]
   479  	if !found {
   480  		return nil
   481  	}
   482  	if err := relationer.SetDying(); err != nil {
   483  		return errors.Trace(err)
   484  	}
   485  	if relationer.IsImplicit() {
   486  		delete(r.relationers, id)
   487  	}
   488  	return nil
   489  }