github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/relations.go (about)

     1  // Copyright 2012-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/names"
     9  	corecharm "gopkg.in/juju/charm.v5"
    10  	"gopkg.in/juju/charm.v5/hooks"
    11  	"launchpad.net/tomb"
    12  
    13  	"github.com/juju/juju/api/uniter"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/state/watcher"
    16  	"github.com/juju/juju/worker/uniter/hook"
    17  	"github.com/juju/juju/worker/uniter/relation"
    18  	"github.com/juju/juju/worker/uniter/runner"
    19  )
    20  
    21  // Relations exists to encapsulate relation state and operations behind an
    22  // interface for the benefit of future refactoring.
    23  type Relations interface {
    24  
    25  	// Name returns the name of the relation with the supplied id, or an error
    26  	// if the relation is unknown.
    27  	Name(id int) (string, error)
    28  
    29  	// Hooks returns the channel on which relation hook execution requests
    30  	// are sent.
    31  	Hooks() <-chan hook.Info
    32  
    33  	// StartHooks starts sending hook execution requests on the Hooks channel.
    34  	StartHooks()
    35  
    36  	// StopHooks stops sending hook execution requests on the Hooks channel.
    37  	StopHooks() error
    38  
    39  	// PrepareHook returns the name of the supplied relation hook, or an error
    40  	// if the hook is unknown or invalid given current state.
    41  	PrepareHook(hookInfo hook.Info) (string, error)
    42  
    43  	// CommitHook persists the state change encoded in the supplied relation
    44  	// hook, or returns an error if the hook is unknown or invalid given
    45  	// current relation state.
    46  	CommitHook(hookInfo hook.Info) error
    47  
    48  	// GetInfo returns information about current relation state.
    49  	GetInfo() map[int]*runner.RelationInfo
    50  
    51  	// Update checks for and responds to changes in the life states of the
    52  	// relations with the supplied ids. If any id corresponds to an alive
    53  	// relation that is not already recorded, the unit will enter scope for
    54  	// that relation and start its hook queue.
    55  	Update(ids []int) error
    56  
    57  	// SetDying notifies all known relations that the only hooks to be requested
    58  	// should be those necessary to cleanly exit the relation.
    59  	SetDying() error
    60  }
    61  
    62  // relations implements Relations.
    63  type relations struct {
    64  	st            *uniter.State
    65  	unit          *uniter.Unit
    66  	charmDir      string
    67  	relationsDir  string
    68  	relationers   map[int]*Relationer
    69  	relationHooks chan hook.Info
    70  	abort         <-chan struct{}
    71  }
    72  
    73  func newRelations(st *uniter.State, tag names.UnitTag, paths Paths, abort <-chan struct{}) (*relations, error) {
    74  	unit, err := st.Unit(tag)
    75  	if err != nil {
    76  		return nil, errors.Trace(err)
    77  	}
    78  	r := &relations{
    79  		st:            st,
    80  		unit:          unit,
    81  		charmDir:      paths.State.CharmDir,
    82  		relationsDir:  paths.State.RelationsDir,
    83  		relationers:   make(map[int]*Relationer),
    84  		relationHooks: make(chan hook.Info),
    85  		abort:         abort,
    86  	}
    87  	if err := r.init(); err != nil {
    88  		return nil, errors.Trace(err)
    89  	}
    90  	return r, nil
    91  }
    92  
    93  // init reconciles the local relation state dirs with the remote state of
    94  // the corresponding relations. It's only expected to be called while a
    95  // *relations is being created.
    96  func (r *relations) init() error {
    97  	joinedRelationTags, err := r.unit.JoinedRelations()
    98  	if err != nil {
    99  		return errors.Trace(err)
   100  	}
   101  	joinedRelations := make(map[int]*uniter.Relation)
   102  	for _, tag := range joinedRelationTags {
   103  		relation, err := r.st.Relation(tag)
   104  		if err != nil {
   105  			return errors.Trace(err)
   106  		}
   107  		joinedRelations[relation.Id()] = relation
   108  	}
   109  	knownDirs, err := relation.ReadAllStateDirs(r.relationsDir)
   110  	if err != nil {
   111  		return errors.Trace(err)
   112  	}
   113  	for id, dir := range knownDirs {
   114  		if rel, ok := joinedRelations[id]; ok {
   115  			if err := r.add(rel, dir); err != nil {
   116  				return errors.Trace(err)
   117  			}
   118  		} else if err := dir.Remove(); err != nil {
   119  			return errors.Trace(err)
   120  		}
   121  	}
   122  	for id, rel := range joinedRelations {
   123  		if _, ok := knownDirs[id]; ok {
   124  			continue
   125  		}
   126  		dir, err := relation.ReadStateDir(r.relationsDir, id)
   127  		if err != nil {
   128  			return errors.Trace(err)
   129  		}
   130  		if err := r.add(rel, dir); err != nil {
   131  			return errors.Trace(err)
   132  		}
   133  	}
   134  	return nil
   135  }
   136  
   137  // Name is part of the Relations interface.
   138  func (r *relations) Name(id int) (string, error) {
   139  	relationer, found := r.relationers[id]
   140  	if !found {
   141  		return "", errors.Errorf("unknown relation: %d", id)
   142  	}
   143  	return relationer.ru.Endpoint().Name, nil
   144  }
   145  
   146  // Hooks is part of the Relations interface.
   147  func (r *relations) Hooks() <-chan hook.Info {
   148  	return r.relationHooks
   149  }
   150  
   151  // StartHooks is part of the Relations interface.
   152  func (r *relations) StartHooks() {
   153  	for _, relationer := range r.relationers {
   154  		relationer.StartHooks()
   155  	}
   156  }
   157  
   158  // StopHooks is part of the Relations interface.
   159  func (r *relations) StopHooks() (err error) {
   160  	for _, relationer := range r.relationers {
   161  		if e := relationer.StopHooks(); e != nil {
   162  			if err == nil {
   163  				err = e
   164  			} else {
   165  				logger.Errorf("additional error while stopping hooks: %v", e)
   166  			}
   167  		}
   168  	}
   169  	return err
   170  }
   171  
   172  // PrepareHook is part of the Relations interface.
   173  func (r *relations) PrepareHook(hookInfo hook.Info) (string, error) {
   174  	if !hookInfo.Kind.IsRelation() {
   175  		return "", errors.Errorf("not a relation hook: %#v", hookInfo)
   176  	}
   177  	relationer, found := r.relationers[hookInfo.RelationId]
   178  	if !found {
   179  		return "", errors.Errorf("unknown relation: %d", hookInfo.RelationId)
   180  	}
   181  	return relationer.PrepareHook(hookInfo)
   182  }
   183  
   184  // CommitHook is part of the Relations interface.
   185  func (r *relations) CommitHook(hookInfo hook.Info) error {
   186  	if !hookInfo.Kind.IsRelation() {
   187  		return errors.Errorf("not a relation hook: %#v", hookInfo)
   188  	}
   189  	relationer, found := r.relationers[hookInfo.RelationId]
   190  	if !found {
   191  		return errors.Errorf("unknown relation: %d", hookInfo.RelationId)
   192  	}
   193  	if hookInfo.Kind == hooks.RelationBroken {
   194  		delete(r.relationers, hookInfo.RelationId)
   195  	}
   196  	return relationer.CommitHook(hookInfo)
   197  }
   198  
   199  // GetInfo is part of the Relations interface.
   200  func (r *relations) GetInfo() map[int]*runner.RelationInfo {
   201  	relationInfos := map[int]*runner.RelationInfo{}
   202  	for id, relationer := range r.relationers {
   203  		relationInfos[id] = relationer.ContextInfo()
   204  	}
   205  	return relationInfos
   206  }
   207  
   208  // Update is part of the Relations interface.
   209  func (r *relations) Update(ids []int) error {
   210  	for _, id := range ids {
   211  		if relationer, found := r.relationers[id]; found {
   212  			rel := relationer.ru.Relation()
   213  			if err := rel.Refresh(); err != nil {
   214  				return errors.Annotatef(err, "cannot update relation %q", rel)
   215  			}
   216  			if rel.Life() == params.Dying {
   217  				if err := r.setDying(id); err != nil {
   218  					return errors.Trace(err)
   219  				}
   220  			}
   221  			continue
   222  		}
   223  		// Relations that are not alive are simply skipped, because they
   224  		// were not previously known anyway.
   225  		rel, err := r.st.RelationById(id)
   226  		if err != nil {
   227  			if params.IsCodeNotFoundOrCodeUnauthorized(err) {
   228  				continue
   229  			}
   230  			return errors.Trace(err)
   231  		}
   232  		if rel.Life() != params.Alive {
   233  			continue
   234  		}
   235  		// Make sure we ignore relations not implemented by the unit's charm.
   236  		ch, err := corecharm.ReadCharmDir(r.charmDir)
   237  		if err != nil {
   238  			return errors.Trace(err)
   239  		}
   240  		if ep, err := rel.Endpoint(); err != nil {
   241  			return errors.Trace(err)
   242  		} else if !ep.ImplementedBy(ch) {
   243  			logger.Warningf("skipping relation with unknown endpoint %q", ep.Name)
   244  			continue
   245  		}
   246  		dir, err := relation.ReadStateDir(r.relationsDir, id)
   247  		if err != nil {
   248  			return errors.Trace(err)
   249  		}
   250  		err = r.add(rel, dir)
   251  		if err == nil {
   252  			r.relationers[id].StartHooks()
   253  			continue
   254  		}
   255  		e := dir.Remove()
   256  		if !params.IsCodeCannotEnterScope(err) {
   257  			return errors.Trace(err)
   258  		}
   259  		if e != nil {
   260  			return errors.Trace(e)
   261  		}
   262  	}
   263  	if ok, err := r.unit.IsPrincipal(); err != nil {
   264  		return errors.Trace(err)
   265  	} else if ok {
   266  		return nil
   267  	}
   268  	// If no Alive relations remain between a subordinate unit's service
   269  	// and its principal's service, the subordinate must become Dying.
   270  	for _, relationer := range r.relationers {
   271  		scope := relationer.ru.Endpoint().Scope
   272  		if scope == corecharm.ScopeContainer && !relationer.dying {
   273  			return nil
   274  		}
   275  	}
   276  	return r.unit.Destroy()
   277  }
   278  
   279  // SetDying is part of the Relations interface.
   280  // should be those necessary to cleanly exit the relation.
   281  func (r *relations) SetDying() error {
   282  	for id := range r.relationers {
   283  		if err := r.setDying(id); err != nil {
   284  			return err
   285  		}
   286  	}
   287  	return nil
   288  }
   289  
   290  // add causes the unit agent to join the supplied relation, and to
   291  // store persistent state in the supplied dir.
   292  func (r *relations) add(rel *uniter.Relation, dir *relation.StateDir) (err error) {
   293  	logger.Infof("joining relation %q", rel)
   294  	ru, err := rel.Unit(r.unit)
   295  	if err != nil {
   296  		return errors.Trace(err)
   297  	}
   298  	relationer := NewRelationer(ru, dir, r.relationHooks)
   299  	w, err := r.unit.Watch()
   300  	if err != nil {
   301  		return errors.Trace(err)
   302  	}
   303  	defer func() {
   304  		if e := w.Stop(); e != nil {
   305  			if err == nil {
   306  				err = e
   307  			} else {
   308  				logger.Errorf("error stopping unit watcher: %v", e)
   309  			}
   310  		}
   311  	}()
   312  	for {
   313  		select {
   314  		case <-r.abort:
   315  			return tomb.ErrDying
   316  		case _, ok := <-w.Changes():
   317  			if !ok {
   318  				return watcher.EnsureErr(w)
   319  			}
   320  			err := relationer.Join()
   321  			if params.IsCodeCannotEnterScopeYet(err) {
   322  				logger.Infof("cannot enter scope for relation %q; waiting for subordinate to be removed", rel)
   323  				continue
   324  			} else if err != nil {
   325  				return errors.Trace(err)
   326  			}
   327  			logger.Infof("joined relation %q", rel)
   328  			r.relationers[rel.Id()] = relationer
   329  			return nil
   330  		}
   331  	}
   332  }
   333  
   334  // setDying notifies the relationer identified by the supplied id that the
   335  // only hook executions to be requested should be those necessary to cleanly
   336  // exit the relation.
   337  func (r *relations) setDying(id int) error {
   338  	relationer, found := r.relationers[id]
   339  	if !found {
   340  		return nil
   341  	}
   342  	if err := relationer.SetDying(); err != nil {
   343  		return errors.Trace(err)
   344  	}
   345  	if relationer.IsImplicit() {
   346  		delete(r.relationers, id)
   347  	}
   348  	return nil
   349  }