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