github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"launchpad.net/tomb"
    12  
    13  	"github.com/juju/juju/agent"
    14  	"github.com/juju/juju/environs"
    15  	"github.com/juju/juju/environs/config"
    16  	"github.com/juju/juju/instance"
    17  	apiprovisioner "github.com/juju/juju/state/api/provisioner"
    18  	apiwatcher "github.com/juju/juju/state/api/watcher"
    19  	"github.com/juju/juju/state/watcher"
    20  	"github.com/juju/juju/worker"
    21  )
    22  
    23  var logger = loggo.GetLogger("juju.provisioner")
    24  
    25  // Ensure our structs implement the required Provisioner interface.
    26  var _ Provisioner = (*environProvisioner)(nil)
    27  var _ Provisioner = (*containerProvisioner)(nil)
    28  
    29  // Provisioner represents a running provisioner worker.
    30  type Provisioner interface {
    31  	worker.Worker
    32  	Stop() error
    33  	getMachineWatcher() (apiwatcher.StringsWatcher, error)
    34  	getRetryWatcher() (apiwatcher.NotifyWatcher, 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, err
   106  	}
   107  	// Start responding to changes in machines, and to any further updates
   108  	// to the environment config.
   109  	machineWatcher, err := p.getMachineWatcher()
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	retryWatcher, err := p.getRetryWatcher()
   114  	if err != nil && !errors.IsNotImplemented(err) {
   115  		return nil, err
   116  	}
   117  	task := NewProvisionerTask(
   118  		p.agentConfig.Tag(), safeMode, p.st,
   119  		machineWatcher, retryWatcher, p.broker, auth)
   120  	return task, nil
   121  }
   122  
   123  // NewEnvironProvisioner returns a new Provisioner for an environment.
   124  // When new machines are added to the state, it allocates instances
   125  // from the environment and allocates them to the new machines.
   126  func NewEnvironProvisioner(st *apiprovisioner.State, agentConfig agent.Config) Provisioner {
   127  	p := &environProvisioner{
   128  		provisioner: provisioner{
   129  			st:          st,
   130  			agentConfig: agentConfig,
   131  		},
   132  	}
   133  	p.Provisioner = p
   134  	logger.Tracef("Starting environ provisioner for %q", p.agentConfig.Tag())
   135  	go func() {
   136  		defer p.tomb.Done()
   137  		p.tomb.Kill(p.loop())
   138  	}()
   139  	return p
   140  }
   141  
   142  func (p *environProvisioner) loop() error {
   143  	var environConfigChanges <-chan struct{}
   144  	environWatcher, err := p.st.WatchForEnvironConfigChanges()
   145  	if err != nil {
   146  		return err
   147  	}
   148  	environConfigChanges = environWatcher.Changes()
   149  	defer watcher.Stop(environWatcher, &p.tomb)
   150  
   151  	p.environ, err = worker.WaitForEnviron(environWatcher, p.st, p.tomb.Dying())
   152  	if err != nil {
   153  		return err
   154  	}
   155  	p.broker = p.environ
   156  
   157  	safeMode := p.environ.Config().ProvisionerSafeMode()
   158  	task, err := p.getStartTask(safeMode)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	defer watcher.Stop(task, &p.tomb)
   163  
   164  	for {
   165  		select {
   166  		case <-p.tomb.Dying():
   167  			return tomb.ErrDying
   168  		case <-task.Dying():
   169  			err := task.Err()
   170  			logger.Errorf("environ provisioner died: %v", err)
   171  			return err
   172  		case _, ok := <-environConfigChanges:
   173  			if !ok {
   174  				return watcher.MustErr(environWatcher)
   175  			}
   176  			environConfig, err := p.st.EnvironConfig()
   177  			if err != nil {
   178  				logger.Errorf("cannot load environment configuration: %v", err)
   179  				return err
   180  			}
   181  			if err := p.setConfig(environConfig); err != nil {
   182  				logger.Errorf("loaded invalid environment configuration: %v", err)
   183  			}
   184  			task.SetSafeMode(environConfig.ProvisionerSafeMode())
   185  		}
   186  	}
   187  }
   188  
   189  func (p *environProvisioner) getMachineWatcher() (apiwatcher.StringsWatcher, error) {
   190  	return p.st.WatchEnvironMachines()
   191  }
   192  
   193  func (p *environProvisioner) getRetryWatcher() (apiwatcher.NotifyWatcher, error) {
   194  	return p.st.WatchMachineErrorRetry()
   195  }
   196  
   197  // setConfig updates the environment configuration and notifies
   198  // the config observer.
   199  func (p *environProvisioner) setConfig(environConfig *config.Config) error {
   200  	if err := p.environ.SetConfig(environConfig); err != nil {
   201  		return err
   202  	}
   203  	p.configObserver.notify(environConfig)
   204  	return nil
   205  }
   206  
   207  // NewContainerProvisioner returns a new Provisioner. When new machines
   208  // are added to the state, it allocates instances from the environment
   209  // and allocates them to the new machines.
   210  func NewContainerProvisioner(containerType instance.ContainerType, st *apiprovisioner.State,
   211  	agentConfig agent.Config, broker environs.InstanceBroker) Provisioner {
   212  
   213  	p := &containerProvisioner{
   214  		provisioner: provisioner{
   215  			st:          st,
   216  			agentConfig: agentConfig,
   217  			broker:      broker,
   218  		},
   219  		containerType: containerType,
   220  	}
   221  	p.Provisioner = p
   222  	logger.Tracef("Starting %s provisioner for %q", p.containerType, p.agentConfig.Tag())
   223  	go func() {
   224  		defer p.tomb.Done()
   225  		p.tomb.Kill(p.loop())
   226  	}()
   227  	return p
   228  }
   229  
   230  func (p *containerProvisioner) loop() error {
   231  	task, err := p.getStartTask(false)
   232  	if err != nil {
   233  		return err
   234  	}
   235  	defer watcher.Stop(task, &p.tomb)
   236  
   237  	for {
   238  		select {
   239  		case <-p.tomb.Dying():
   240  			return tomb.ErrDying
   241  		case <-task.Dying():
   242  			err := task.Err()
   243  			logger.Errorf("%s provisioner died: %v", p.containerType, err)
   244  			return err
   245  		}
   246  	}
   247  }
   248  
   249  func (p *containerProvisioner) getMachine() (*apiprovisioner.Machine, error) {
   250  	if p.machine == nil {
   251  		var err error
   252  		if p.machine, err = p.st.Machine(p.agentConfig.Tag()); err != nil {
   253  			logger.Errorf("%s is not in state", p.agentConfig.Tag())
   254  			return nil, err
   255  		}
   256  	}
   257  	return p.machine, nil
   258  }
   259  
   260  func (p *containerProvisioner) getMachineWatcher() (apiwatcher.StringsWatcher, error) {
   261  	machine, err := p.getMachine()
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	return machine.WatchContainers(p.containerType)
   266  }
   267  
   268  func (p *containerProvisioner) getRetryWatcher() (apiwatcher.NotifyWatcher, error) {
   269  	return nil, errors.NotImplementedf("getRetryWatcher")
   270  }