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