github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/deployer/deployer.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package deployer
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/loggo"
    10  	"github.com/juju/names"
    11  	"github.com/juju/utils"
    12  	"github.com/juju/utils/set"
    13  
    14  	"github.com/juju/juju/agent"
    15  	apideployer "github.com/juju/juju/state/api/deployer"
    16  	"github.com/juju/juju/state/api/params"
    17  	"github.com/juju/juju/state/api/watcher"
    18  	"github.com/juju/juju/worker"
    19  )
    20  
    21  var logger = loggo.GetLogger("juju.worker.deployer")
    22  
    23  // Deployer is responsible for deploying and recalling unit agents, according
    24  // to changes in a set of state units; and for the final removal of its agents'
    25  // units from state when they are no longer needed.
    26  type Deployer struct {
    27  	st       *apideployer.State
    28  	ctx      Context
    29  	deployed set.Strings
    30  }
    31  
    32  // Context abstracts away the differences between different unit deployment
    33  // strategies; where a Deployer is responsible for what to deploy, a Context
    34  // is responsible for how to deploy.
    35  type Context interface {
    36  	// DeployUnit causes the agent for the specified unit to be started and run
    37  	// continuously until further notice without further intervention. It will
    38  	// return an error if the agent is already deployed.
    39  	DeployUnit(unitName, initialPassword string) error
    40  
    41  	// RecallUnit causes the agent for the specified unit to be stopped, and
    42  	// the agent's data to be destroyed. It will return an error if the agent
    43  	// was not deployed by the manager.
    44  	RecallUnit(unitName string) error
    45  
    46  	// DeployedUnits returns the names of all units deployed by the manager.
    47  	DeployedUnits() ([]string, error)
    48  
    49  	// AgentConfig returns the agent config for the machine agent that is
    50  	// running the deployer.
    51  	AgentConfig() agent.Config
    52  }
    53  
    54  // NewDeployer returns a Worker that deploys and recalls unit agents
    55  // via ctx, taking a machine id to operate on.
    56  func NewDeployer(st *apideployer.State, ctx Context) worker.Worker {
    57  	d := &Deployer{
    58  		st:  st,
    59  		ctx: ctx,
    60  	}
    61  	return worker.NewStringsWorker(d)
    62  }
    63  
    64  func (d *Deployer) SetUp() (watcher.StringsWatcher, error) {
    65  	machineTag := d.ctx.AgentConfig().Tag()
    66  	machine, err := d.st.Machine(machineTag)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	machineUnitsWatcher, err := machine.WatchUnits()
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	deployed, err := d.ctx.DeployedUnits()
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	for _, unitName := range deployed {
    80  		d.deployed.Add(unitName)
    81  		if err := d.changed(unitName); err != nil {
    82  			return nil, err
    83  		}
    84  	}
    85  	return machineUnitsWatcher, nil
    86  }
    87  
    88  func (d *Deployer) Handle(unitNames []string) error {
    89  	for _, unitName := range unitNames {
    90  		if err := d.changed(unitName); err != nil {
    91  			return err
    92  		}
    93  	}
    94  	return nil
    95  }
    96  
    97  // changed ensures that the named unit is deployed, recalled, or removed, as
    98  // indicated by its state.
    99  func (d *Deployer) changed(unitName string) error {
   100  	unitTag := names.UnitTag(unitName)
   101  	// Determine unit life state, and whether we're responsible for it.
   102  	logger.Infof("checking unit %q", unitName)
   103  	var life params.Life
   104  	unit, err := d.st.Unit(unitTag)
   105  	if params.IsCodeNotFoundOrCodeUnauthorized(err) {
   106  		life = params.Dead
   107  	} else if err != nil {
   108  		return err
   109  	} else {
   110  		life = unit.Life()
   111  	}
   112  	// Deployed units must be removed if they're Dead, or if the deployer
   113  	// is no longer responsible for them.
   114  	if d.deployed.Contains(unitName) {
   115  		if life == params.Dead {
   116  			if err := d.recall(unitName); err != nil {
   117  				return err
   118  			}
   119  		}
   120  	}
   121  	// The only units that should be deployed are those that (1) we are responsible
   122  	// for and (2) are Alive -- if we're responsible for a Dying unit that is not
   123  	// yet deployed, we should remove it immediately rather than undergo the hassle
   124  	// of deploying a unit agent purely so it can set itself to Dead.
   125  	if !d.deployed.Contains(unitName) {
   126  		if life == params.Alive {
   127  			return d.deploy(unit)
   128  		} else if unit != nil {
   129  			return d.remove(unit)
   130  		}
   131  	}
   132  	return nil
   133  }
   134  
   135  // deploy will deploy the supplied unit with the deployer's manager. It will
   136  // panic if it observes inconsistent internal state.
   137  func (d *Deployer) deploy(unit *apideployer.Unit) error {
   138  	unitName := unit.Name()
   139  	if d.deployed.Contains(unit.Name()) {
   140  		panic("must not re-deploy a deployed unit")
   141  	}
   142  	logger.Infof("deploying unit %q", unitName)
   143  	initialPassword, err := utils.RandomPassword()
   144  	if err != nil {
   145  		return err
   146  	}
   147  	if err := unit.SetPassword(initialPassword); err != nil {
   148  		return fmt.Errorf("cannot set password for unit %q: %v", unitName, err)
   149  	}
   150  	if err := d.ctx.DeployUnit(unitName, initialPassword); err != nil {
   151  		return err
   152  	}
   153  	d.deployed.Add(unitName)
   154  	return nil
   155  }
   156  
   157  // recall will recall the named unit with the deployer's manager. It will
   158  // panic if it observes inconsistent internal state.
   159  func (d *Deployer) recall(unitName string) error {
   160  	if !d.deployed.Contains(unitName) {
   161  		panic("must not recall a unit that is not deployed")
   162  	}
   163  	logger.Infof("recalling unit %q", unitName)
   164  	if err := d.ctx.RecallUnit(unitName); err != nil {
   165  		return err
   166  	}
   167  	d.deployed.Remove(unitName)
   168  	return nil
   169  }
   170  
   171  // remove will remove the supplied unit from state. It will panic if it
   172  // observes inconsistent internal state.
   173  func (d *Deployer) remove(unit *apideployer.Unit) error {
   174  	unitName := unit.Name()
   175  	if d.deployed.Contains(unitName) {
   176  		panic("must not remove a deployed unit")
   177  	} else if unit.Life() == params.Alive {
   178  		panic("must not remove an Alive unit")
   179  	}
   180  	logger.Infof("removing unit %q", unitName)
   181  	return unit.Remove()
   182  }
   183  
   184  func (d *Deployer) TearDown() error {
   185  	// Nothing to do here.
   186  	return nil
   187  }