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

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"gopkg.in/juju/charm.v5/hooks"
    10  
    11  	apiuniter "github.com/juju/juju/api/uniter"
    12  	"github.com/juju/juju/worker/uniter/hook"
    13  	"github.com/juju/juju/worker/uniter/relation"
    14  	"github.com/juju/juju/worker/uniter/runner"
    15  )
    16  
    17  // Relationer manages a unit's presence in a relation.
    18  type Relationer struct {
    19  	ru    *apiuniter.RelationUnit
    20  	dir   *relation.StateDir
    21  	queue relation.HookQueue
    22  	hooks chan<- hook.Info
    23  	dying bool
    24  }
    25  
    26  // NewRelationer creates a new Relationer. The unit will not join the
    27  // relation until explicitly requested.
    28  func NewRelationer(ru *apiuniter.RelationUnit, dir *relation.StateDir, hooks chan<- hook.Info) *Relationer {
    29  	return &Relationer{
    30  		ru:    ru,
    31  		dir:   dir,
    32  		hooks: hooks,
    33  	}
    34  }
    35  
    36  // ContextInfo returns a represention of r's current state.
    37  func (r *Relationer) ContextInfo() *runner.RelationInfo {
    38  	members := r.dir.State().Members
    39  	memberNames := make([]string, 0, len(members))
    40  	for memberName := range members {
    41  		memberNames = append(memberNames, memberName)
    42  	}
    43  	return &runner.RelationInfo{r.ru, memberNames}
    44  }
    45  
    46  // IsImplicit returns whether the local relation endpoint is implicit. Implicit
    47  // relations do not run hooks.
    48  func (r *Relationer) IsImplicit() bool {
    49  	return r.ru.Endpoint().IsImplicit()
    50  }
    51  
    52  // Join initializes local state and causes the unit to enter its relation
    53  // scope, allowing its counterpart units to detect its presence and settings
    54  // changes. Local state directory is not created until needed.
    55  func (r *Relationer) Join() error {
    56  	if r.dying {
    57  		panic("dying relationer must not join!")
    58  	}
    59  	// We need to make sure the state directory exists before we join the
    60  	// relation, lest a subsequent ReadAllStateDirs report local state that
    61  	// doesn't include relations recorded in remote state.
    62  	if err := r.dir.Ensure(); err != nil {
    63  		return err
    64  	}
    65  	// uniter.RelationUnit.EnterScope() sets the unit's private address
    66  	// internally automatically, so no need to set it here.
    67  	return r.ru.EnterScope()
    68  }
    69  
    70  // SetDying informs the relationer that the unit is departing the relation,
    71  // and that the only hooks it should send henceforth are -departed hooks,
    72  // until the relation is empty, followed by a -broken hook.
    73  func (r *Relationer) SetDying() error {
    74  	if r.IsImplicit() {
    75  		r.dying = true
    76  		return r.die()
    77  	}
    78  	if r.queue != nil {
    79  		if err := r.StopHooks(); err != nil {
    80  			return err
    81  		}
    82  		defer r.StartHooks()
    83  	}
    84  	r.dying = true
    85  	return nil
    86  }
    87  
    88  // die is run when the relationer has no further responsibilities; it leaves
    89  // relation scope, and removes the local relation state directory.
    90  func (r *Relationer) die() error {
    91  	if err := r.ru.LeaveScope(); err != nil {
    92  		return err
    93  	}
    94  	return r.dir.Remove()
    95  }
    96  
    97  // StartHooks starts watching the relation, and sending hook.Info events on the
    98  // hooks channel. It will panic if called when already responding to relation
    99  // changes.
   100  func (r *Relationer) StartHooks() error {
   101  	if r.IsImplicit() {
   102  		return nil
   103  	}
   104  	if r.queue != nil {
   105  		panic("hooks already started!")
   106  	}
   107  	if r.dying {
   108  		r.queue = relation.NewDyingHookQueue(r.dir.State(), r.hooks)
   109  	} else {
   110  		w, err := r.ru.Watch()
   111  		if err != nil {
   112  			return err
   113  		}
   114  		r.queue = relation.NewAliveHookQueue(r.dir.State(), r.hooks, w)
   115  	}
   116  	return nil
   117  }
   118  
   119  // StopHooks ensures that the relationer is not watching the relation, or sending
   120  // hook.Info events on the hooks channel.
   121  func (r *Relationer) StopHooks() error {
   122  	if r.queue == nil {
   123  		return nil
   124  	}
   125  	queue := r.queue
   126  	r.queue = nil
   127  	return queue.Stop()
   128  }
   129  
   130  // PrepareHook checks that the relation is in a state such that it makes
   131  // sense to execute the supplied hook, and ensures that the relation context
   132  // contains the latest relation state as communicated in the hook.Info. It
   133  // returns the name of the hook that must be run.
   134  func (r *Relationer) PrepareHook(hi hook.Info) (hookName string, err error) {
   135  	if r.IsImplicit() {
   136  		panic("implicit relations must not run hooks")
   137  	}
   138  	if err = r.dir.State().Validate(hi); err != nil {
   139  		return
   140  	}
   141  	name := r.ru.Endpoint().Name
   142  	return fmt.Sprintf("%s-%s", name, hi.Kind), nil
   143  }
   144  
   145  // CommitHook persists the fact of the supplied hook's completion.
   146  func (r *Relationer) CommitHook(hi hook.Info) error {
   147  	if r.IsImplicit() {
   148  		panic("implicit relations must not run hooks")
   149  	}
   150  	if hi.Kind == hooks.RelationBroken {
   151  		return r.die()
   152  	}
   153  	return r.dir.Write(hi)
   154  }