launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/provisioner/provisioner.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner
     5  
     6  import (
     7  	"sync"
     8  
     9  	"github.com/loggo/loggo"
    10  	"launchpad.net/errgo/errors"
    11  	"launchpad.net/tomb"
    12  
    13  	"launchpad.net/juju-core/agent"
    14  	"launchpad.net/juju-core/environs"
    15  	"launchpad.net/juju-core/environs/config"
    16  	"launchpad.net/juju-core/instance"
    17  	apiprovisioner "launchpad.net/juju-core/state/api/provisioner"
    18  	"launchpad.net/juju-core/state/watcher"
    19  	"launchpad.net/juju-core/worker"
    20  )
    21  
    22  var logger = loggo.GetLogger("juju.provisioner")
    23  
    24  var mask = errors.Mask
    25  
    26  // Ensure our structs implement the required Provisioner interface.
    27  var _ Provisioner = (*environProvisioner)(nil)
    28  var _ Provisioner = (*containerProvisioner)(nil)
    29  
    30  // Provisioner represents a running provisioner worker.
    31  type Provisioner interface {
    32  	worker.Worker
    33  	Stop() error
    34  	getWatcher() (Watcher, error)
    35  }
    36  
    37  // environProvisioner represents a running provisioning worker for machine nodes
    38  // belonging to an environment.
    39  type environProvisioner struct {
    40  	provisioner
    41  	environ environs.Environ
    42  	configObserver
    43  }
    44  
    45  // containerProvisioner represents a running provisioning worker for containers
    46  // hosted on a machine.
    47  type containerProvisioner struct {
    48  	provisioner
    49  	containerType instance.ContainerType
    50  	machine       *apiprovisioner.Machine
    51  }
    52  
    53  // provisioner providers common behaviour for a running provisioning worker.
    54  type provisioner struct {
    55  	Provisioner
    56  	st          *apiprovisioner.State
    57  	agentConfig agent.Config
    58  	broker      environs.InstanceBroker
    59  	tomb        tomb.Tomb
    60  }
    61  
    62  // configObserver is implemented so that tests can see
    63  // when the environment configuration changes.
    64  type configObserver struct {
    65  	sync.Mutex
    66  	observer chan<- *config.Config
    67  }
    68  
    69  // notify notifies the observer of a configuration change.
    70  func (o *configObserver) notify(cfg *config.Config) {
    71  	o.Lock()
    72  	if o.observer != nil {
    73  		o.observer <- cfg
    74  	}
    75  	o.Unlock()
    76  }
    77  
    78  // Err returns the reason why the provisioner has stopped or tomb.ErrStillAlive
    79  // when it is still alive.
    80  func (p *provisioner) Err() (reason error) {
    81  	return p.tomb.Err()
    82  }
    83  
    84  // Kill implements worker.Worker.Kill.
    85  func (p *provisioner) Kill() {
    86  	p.tomb.Kill(nil)
    87  }
    88  
    89  // Wait implements worker.Worker.Wait.
    90  func (p *provisioner) Wait() error {
    91  	return p.tomb.Wait()
    92  }
    93  
    94  // Stop stops the provisioner and returns any error encountered while
    95  // provisioning.
    96  func (p *provisioner) Stop() error {
    97  	p.tomb.Kill(nil)
    98  	return p.tomb.Wait()
    99  }
   100  
   101  // getStartTask creates a new worker for the provisioner,
   102  func (p *provisioner) getStartTask(safeMode bool) (ProvisionerTask, error) {
   103  	auth, err := environs.NewAPIAuthenticator(p.st)
   104  	if err != nil {
   105  		return nil, mask(err)
   106  	}
   107  
   108  	// Start responding to changes in machines, and to any further updates
   109  	// to the environment config.
   110  	machineWatcher, err := p.getWatcher()
   111  	if err != nil {
   112  		return nil, mask(err)
   113  	}
   114  	task := NewProvisionerTask(
   115  		p.agentConfig.Tag(),
   116  		safeMode,
   117  		p.st,
   118  		machineWatcher,
   119  		p.broker,
   120  		auth)
   121  	return task, nil
   122  }
   123  
   124  // NewEnvironProvisioner returns a new Provisioner for an environment.
   125  // When new machines are added to the state, it allocates instances
   126  // from the environment and allocates them to the new machines.
   127  func NewEnvironProvisioner(st *apiprovisioner.State, agentConfig agent.Config) Provisioner {
   128  	p := &environProvisioner{
   129  		provisioner: provisioner{
   130  			st:          st,
   131  			agentConfig: agentConfig,
   132  		},
   133  	}
   134  	p.Provisioner = p
   135  	logger.Tracef("Starting environ provisioner for %q", p.agentConfig.Tag())
   136  	go func() {
   137  		defer p.tomb.Done()
   138  		killTomb(&p.tomb, p.loop())
   139  	}()
   140  	return p
   141  }
   142  
   143  func killTomb(t *tomb.Tomb, err error) {
   144  	if errors.Cause(err) == tomb.ErrDying {
   145  		return
   146  	}
   147  	t.Kill(err)
   148  }
   149  
   150  func (p *environProvisioner) loop() error {
   151  	var environConfigChanges <-chan struct{}
   152  	environWatcher, err := p.st.WatchForEnvironConfigChanges()
   153  	if err != nil {
   154  		return mask(err)
   155  	}
   156  	environConfigChanges = environWatcher.Changes()
   157  	defer watcher.Stop(environWatcher, &p.tomb)
   158  
   159  	p.environ, err = worker.WaitForEnviron(environWatcher, p.st, p.tomb.Dying())
   160  	if err != nil {
   161  		return mask(err, errors.Is(tomb.ErrDying))
   162  	}
   163  	p.broker = p.environ
   164  
   165  	safeMode := p.environ.Config().ProvisionerSafeMode()
   166  	task, err := p.getStartTask(safeMode)
   167  	if err != nil {
   168  		return mask(err)
   169  	}
   170  	defer watcher.Stop(task, &p.tomb)
   171  
   172  	for {
   173  		select {
   174  		case <-p.tomb.Dying():
   175  			return tomb.ErrDying
   176  		case <-task.Dying():
   177  			err := task.Err()
   178  			logger.Errorf("environ provisioner died: %v", err)
   179  			return err
   180  		case _, ok := <-environConfigChanges:
   181  			if !ok {
   182  				return watcher.MustErr(environWatcher)
   183  			}
   184  			environConfig, err := p.st.EnvironConfig()
   185  			if err != nil {
   186  				logger.Errorf("cannot load environment configuration: %v", err)
   187  				return err
   188  			}
   189  			if err := p.setConfig(environConfig); err != nil {
   190  				logger.Errorf("loaded invalid environment configuration: %v", err)
   191  			}
   192  			task.SetSafeMode(environConfig.ProvisionerSafeMode())
   193  		}
   194  	}
   195  }
   196  
   197  func (p *environProvisioner) getWatcher() (Watcher, error) {
   198  	return p.st.WatchEnvironMachines()
   199  }
   200  
   201  // setConfig updates the environment configuration and notifies
   202  // the config observer.
   203  func (p *environProvisioner) setConfig(environConfig *config.Config) error {
   204  	if err := p.environ.SetConfig(environConfig); err != nil {
   205  		return mask(err)
   206  	}
   207  	p.configObserver.notify(environConfig)
   208  	return nil
   209  }
   210  
   211  // NewContainerProvisioner returns a new Provisioner. When new machines
   212  // are added to the state, it allocates instances from the environment
   213  // and allocates them to the new machines.
   214  func NewContainerProvisioner(containerType instance.ContainerType, st *apiprovisioner.State,
   215  	agentConfig agent.Config, broker environs.InstanceBroker) Provisioner {
   216  
   217  	p := &containerProvisioner{
   218  		provisioner: provisioner{
   219  			st:          st,
   220  			agentConfig: agentConfig,
   221  			broker:      broker,
   222  		},
   223  		containerType: containerType,
   224  	}
   225  	p.Provisioner = p
   226  	logger.Tracef("Starting %s provisioner for %q", p.containerType, p.agentConfig.Tag())
   227  	go func() {
   228  		defer p.tomb.Done()
   229  		killTomb(&p.tomb, p.loop())
   230  	}()
   231  	return p
   232  }
   233  
   234  func (p *containerProvisioner) loop() error {
   235  	task, err := p.getStartTask(false)
   236  	if err != nil {
   237  		return mask(err)
   238  	}
   239  	defer watcher.Stop(task, &p.tomb)
   240  
   241  	for {
   242  		select {
   243  		case <-p.tomb.Dying():
   244  			return tomb.ErrDying
   245  		case <-task.Dying():
   246  			err := task.Err()
   247  			logger.Errorf("%s provisioner died: %v", p.containerType, err)
   248  			return err
   249  		}
   250  	}
   251  }
   252  
   253  func (p *containerProvisioner) getMachine() (*apiprovisioner.Machine, error) {
   254  	if p.machine == nil {
   255  		var err error
   256  		if p.machine, err = p.st.Machine(p.agentConfig.Tag()); err != nil {
   257  			logger.Errorf("%s is not in state", p.agentConfig.Tag())
   258  			return nil, err
   259  		}
   260  	}
   261  	return p.machine, nil
   262  }
   263  
   264  func (p *containerProvisioner) getWatcher() (Watcher, error) {
   265  	machine, err := p.getMachine()
   266  	if err != nil {
   267  		return nil, mask(err)
   268  	}
   269  	return machine.WatchContainers(p.containerType)
   270  }