launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  	"launchpad.net/juju-core/charm/hooks"
    10  	apiuniter "launchpad.net/juju-core/state/api/uniter"
    11  	"launchpad.net/juju-core/worker/uniter/hook"
    12  	"launchpad.net/juju-core/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  	// uniter.RelationUnit.EnterScope() sets the unit's private address
    55  	// internally automatically, so no need to set it here.
    56  	return r.ru.EnterScope()
    57  }
    58  
    59  // SetDying informs the relationer that the unit is departing the relation,
    60  // and that the only hooks it should send henceforth are -departed hooks,
    61  // until the relation is empty, followed by a -broken hook.
    62  func (r *Relationer) SetDying() error {
    63  	if r.IsImplicit() {
    64  		r.dying = true
    65  		return r.die()
    66  	}
    67  	if r.queue != nil {
    68  		if err := r.StopHooks(); err != nil {
    69  			return mask(err)
    70  		}
    71  		defer r.StartHooks()
    72  	}
    73  	r.dying = true
    74  	return nil
    75  }
    76  
    77  // die is run when the relationer has no further responsibilities; it leaves
    78  // relation scope, and removes the local relation state directory.
    79  func (r *Relationer) die() error {
    80  	if err := r.ru.LeaveScope(); err != nil {
    81  		return mask(err)
    82  	}
    83  	return r.dir.Remove()
    84  }
    85  
    86  // StartHooks starts watching the relation, and sending hook.Info events on the
    87  // hooks channel. It will panic if called when already responding to relation
    88  // changes.
    89  func (r *Relationer) StartHooks() error {
    90  	if r.IsImplicit() {
    91  		return nil
    92  	}
    93  	if r.queue != nil {
    94  		panic("hooks already started!")
    95  	}
    96  	if r.dying {
    97  		r.queue = relation.NewDyingHookQueue(r.dir.State(), r.hooks)
    98  	} else {
    99  		w, err := r.ru.Watch()
   100  		if err != nil {
   101  			return mask(err)
   102  		}
   103  		r.queue = relation.NewAliveHookQueue(r.dir.State(), r.hooks, w)
   104  	}
   105  	return nil
   106  }
   107  
   108  // StopHooks ensures that the relationer is not watching the relation, or sending
   109  // hook.Info events on the hooks channel.
   110  func (r *Relationer) StopHooks() error {
   111  	if r.queue == nil {
   112  		return nil
   113  	}
   114  	queue := r.queue
   115  	r.queue = nil
   116  	return queue.Stop()
   117  }
   118  
   119  // PrepareHook checks that the relation is in a state such that it makes
   120  // sense to execute the supplied hook, and ensures that the relation context
   121  // contains the latest relation state as communicated in the hook.Info. It
   122  // returns the name of the hook that must be run.
   123  func (r *Relationer) PrepareHook(hi hook.Info) (hookName string, err error) {
   124  	if r.IsImplicit() {
   125  		panic("implicit relations must not run hooks")
   126  	}
   127  	if err = r.dir.State().Validate(hi); err != nil {
   128  		return
   129  	}
   130  	// We are about to use the dir, ensure it's there.
   131  	if err = r.dir.Ensure(); err != nil {
   132  		return
   133  	}
   134  	if hi.Kind == hooks.RelationDeparted {
   135  		r.ctx.DeleteMember(hi.RemoteUnit)
   136  	} else if hi.RemoteUnit != "" {
   137  		r.ctx.UpdateMembers(SettingsMap{hi.RemoteUnit: nil})
   138  	}
   139  	name := r.ru.Endpoint().Name
   140  	return fmt.Sprintf("%s-%s", name, hi.Kind), nil
   141  }
   142  
   143  // CommitHook persists the fact of the supplied hook's completion.
   144  func (r *Relationer) CommitHook(hi hook.Info) error {
   145  	if r.IsImplicit() {
   146  		panic("implicit relations must not run hooks")
   147  	}
   148  	if hi.Kind == hooks.RelationBroken {
   149  		return r.die()
   150  	}
   151  	return r.dir.Write(hi)
   152  }