github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	"github.com/juju/juju/charm/hooks"
    10  	apiuniter "github.com/juju/juju/state/api/uniter"
    11  	"github.com/juju/juju/worker/uniter/hook"
    12  	"github.com/juju/juju/worker/uniter/relation"
    13  )
    14  
    15  // Relationer manages a unit's presence in a relation.
    16  type Relationer struct {
    17  	ctx   *ContextRelation
    18  	ru    *apiuniter.RelationUnit
    19  	dir   *relation.StateDir
    20  	queue relation.HookQueue
    21  	hooks chan<- hook.Info
    22  	dying bool
    23  }
    24  
    25  // NewRelationer creates a new Relationer. The unit will not join the
    26  // relation until explicitly requested.
    27  func NewRelationer(ru *apiuniter.RelationUnit, dir *relation.StateDir, hooks chan<- hook.Info) *Relationer {
    28  	return &Relationer{
    29  		ctx:   NewContextRelation(ru, dir.State().Members),
    30  		ru:    ru,
    31  		dir:   dir,
    32  		hooks: hooks,
    33  	}
    34  }
    35  
    36  // Context returns the ContextRelation associated with r.
    37  func (r *Relationer) Context() *ContextRelation {
    38  	return r.ctx
    39  }
    40  
    41  // IsImplicit returns whether the local relation endpoint is implicit. Implicit
    42  // relations do not run hooks.
    43  func (r *Relationer) IsImplicit() bool {
    44  	return r.ru.Endpoint().IsImplicit()
    45  }
    46  
    47  // Join initializes local state and causes the unit to enter its relation
    48  // scope, allowing its counterpart units to detect its presence and settings
    49  // changes. Local state directory is not created until needed.
    50  func (r *Relationer) Join() error {
    51  	if r.dying {
    52  		panic("dying relationer must not join!")
    53  	}
    54  	// We need to make sure the state directory exists before we join the
    55  	// relation, lest a subsequent ReadAllStateDirs report local state that
    56  	// doesn't include relations recorded in remote state.
    57  	if err := r.dir.Ensure(); err != nil {
    58  		return err
    59  	}
    60  	// uniter.RelationUnit.EnterScope() sets the unit's private address
    61  	// internally automatically, so no need to set it here.
    62  	return r.ru.EnterScope()
    63  }
    64  
    65  // SetDying informs the relationer that the unit is departing the relation,
    66  // and that the only hooks it should send henceforth are -departed hooks,
    67  // until the relation is empty, followed by a -broken hook.
    68  func (r *Relationer) SetDying() error {
    69  	if r.IsImplicit() {
    70  		r.dying = true
    71  		return r.die()
    72  	}
    73  	if r.queue != nil {
    74  		if err := r.StopHooks(); err != nil {
    75  			return err
    76  		}
    77  		defer r.StartHooks()
    78  	}
    79  	r.dying = true
    80  	return nil
    81  }
    82  
    83  // die is run when the relationer has no further responsibilities; it leaves
    84  // relation scope, and removes the local relation state directory.
    85  func (r *Relationer) die() error {
    86  	if err := r.ru.LeaveScope(); err != nil {
    87  		return err
    88  	}
    89  	return r.dir.Remove()
    90  }
    91  
    92  // StartHooks starts watching the relation, and sending hook.Info events on the
    93  // hooks channel. It will panic if called when already responding to relation
    94  // changes.
    95  func (r *Relationer) StartHooks() error {
    96  	if r.IsImplicit() {
    97  		return nil
    98  	}
    99  	if r.queue != nil {
   100  		panic("hooks already started!")
   101  	}
   102  	if r.dying {
   103  		r.queue = relation.NewDyingHookQueue(r.dir.State(), r.hooks)
   104  	} else {
   105  		w, err := r.ru.Watch()
   106  		if err != nil {
   107  			return err
   108  		}
   109  		r.queue = relation.NewAliveHookQueue(r.dir.State(), r.hooks, w)
   110  	}
   111  	return nil
   112  }
   113  
   114  // StopHooks ensures that the relationer is not watching the relation, or sending
   115  // hook.Info events on the hooks channel.
   116  func (r *Relationer) StopHooks() error {
   117  	if r.queue == nil {
   118  		return nil
   119  	}
   120  	queue := r.queue
   121  	r.queue = nil
   122  	return queue.Stop()
   123  }
   124  
   125  // PrepareHook checks that the relation is in a state such that it makes
   126  // sense to execute the supplied hook, and ensures that the relation context
   127  // contains the latest relation state as communicated in the hook.Info. It
   128  // returns the name of the hook that must be run.
   129  func (r *Relationer) PrepareHook(hi hook.Info) (hookName string, err error) {
   130  	if r.IsImplicit() {
   131  		panic("implicit relations must not run hooks")
   132  	}
   133  	if err = r.dir.State().Validate(hi); err != nil {
   134  		return
   135  	}
   136  	if hi.Kind == hooks.RelationDeparted {
   137  		r.ctx.DeleteMember(hi.RemoteUnit)
   138  	} else if hi.RemoteUnit != "" {
   139  		r.ctx.UpdateMembers(SettingsMap{hi.RemoteUnit: nil})
   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  }