github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	"github.com/juju/juju/agent"
    15  	apiprovisioner "github.com/juju/juju/api/provisioner"
    16  	"github.com/juju/juju/controller/authentication"
    17  	"github.com/juju/juju/environs"
    18  	"github.com/juju/juju/environs/config"
    19  	"github.com/juju/juju/instance"
    20  	"github.com/juju/juju/watcher"
    21  	"github.com/juju/juju/worker"
    22  	"github.com/juju/juju/worker/catacomb"
    23  )
    24  
    25  var logger = loggo.GetLogger("juju.provisioner")
    26  
    27  // Ensure our structs implement the required Provisioner interface.
    28  var _ Provisioner = (*environProvisioner)(nil)
    29  var _ Provisioner = (*containerProvisioner)(nil)
    30  
    31  var (
    32  	retryStrategyDelay = 10 * time.Second
    33  	retryStrategyCount = 3
    34  )
    35  
    36  // Provisioner represents a running provisioner worker.
    37  type Provisioner interface {
    38  	worker.Worker
    39  	getMachineWatcher() (watcher.StringsWatcher, error)
    40  	getRetryWatcher() (watcher.NotifyWatcher, error)
    41  }
    42  
    43  // environProvisioner represents a running provisioning worker for machine nodes
    44  // belonging to an environment.
    45  type environProvisioner struct {
    46  	provisioner
    47  	environ environs.Environ
    48  	configObserver
    49  }
    50  
    51  // containerProvisioner represents a running provisioning worker for containers
    52  // hosted on a machine.
    53  type containerProvisioner struct {
    54  	provisioner
    55  	containerType instance.ContainerType
    56  	machine       *apiprovisioner.Machine
    57  	configObserver
    58  }
    59  
    60  // provisioner providers common behaviour for a running provisioning worker.
    61  type provisioner struct {
    62  	Provisioner
    63  	st          *apiprovisioner.State
    64  	agentConfig agent.Config
    65  	broker      environs.InstanceBroker
    66  	toolsFinder ToolsFinder
    67  	catacomb    catacomb.Catacomb
    68  }
    69  
    70  // RetryStrategy defines the retry behavior when encountering a retryable
    71  // error during provisioning.
    72  //
    73  // TODO(katco): 2016-08-09: lp:1611427
    74  type RetryStrategy struct {
    75  	retryDelay time.Duration
    76  	retryCount int
    77  }
    78  
    79  // NewRetryStrategy returns a new retry strategy with the specified delay and
    80  // count for use with retryable provisioning errors.
    81  func NewRetryStrategy(delay time.Duration, count int) RetryStrategy {
    82  	return RetryStrategy{
    83  		retryDelay: delay,
    84  		retryCount: count,
    85  	}
    86  }
    87  
    88  // configObserver is implemented so that tests can see
    89  // when the environment configuration changes.
    90  type configObserver struct {
    91  	sync.Mutex
    92  	observer chan<- *config.Config
    93  }
    94  
    95  // notify notifies the observer of a configuration change.
    96  func (o *configObserver) notify(cfg *config.Config) {
    97  	o.Lock()
    98  	if o.observer != nil {
    99  		o.observer <- cfg
   100  	}
   101  	o.Unlock()
   102  }
   103  
   104  // Kill implements worker.Worker.Kill.
   105  func (p *provisioner) Kill() {
   106  	p.catacomb.Kill(nil)
   107  }
   108  
   109  // Wait implements worker.Worker.Wait.
   110  func (p *provisioner) Wait() error {
   111  	return p.catacomb.Wait()
   112  }
   113  
   114  // getToolsFinder returns a ToolsFinder for the provided State.
   115  // This exists for mocking.
   116  var getToolsFinder = func(st *apiprovisioner.State) ToolsFinder {
   117  	return st
   118  }
   119  
   120  // getStartTask creates a new worker for the provisioner,
   121  func (p *provisioner) getStartTask(harvestMode config.HarvestMode) (ProvisionerTask, error) {
   122  	auth, err := authentication.NewAPIAuthenticator(p.st)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	// Start responding to changes in machines, and to any further updates
   127  	// to the environment config.
   128  	machineWatcher, err := p.getMachineWatcher()
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	retryWatcher, err := p.getRetryWatcher()
   133  	if err != nil && !errors.IsNotImplemented(err) {
   134  		return nil, err
   135  	}
   136  	tag := p.agentConfig.Tag()
   137  	machineTag, ok := tag.(names.MachineTag)
   138  	if !ok {
   139  		errors.Errorf("expected names.MachineTag, got %T", tag)
   140  	}
   141  
   142  	modelCfg, err := p.st.ModelConfig()
   143  	if err != nil {
   144  		return nil, errors.Annotate(err, "could not retrieve the model config.")
   145  	}
   146  
   147  	controllerCfg, err := p.st.ControllerConfig()
   148  	if err != nil {
   149  		return nil, errors.Annotate(err, "could not retrieve the controller config.")
   150  	}
   151  
   152  	task, err := NewProvisionerTask(
   153  		controllerCfg.ControllerUUID(),
   154  		machineTag,
   155  		harvestMode,
   156  		p.st,
   157  		p.toolsFinder,
   158  		machineWatcher,
   159  		retryWatcher,
   160  		p.broker,
   161  		auth,
   162  		modelCfg.ImageStream(),
   163  		RetryStrategy{retryDelay: retryStrategyDelay, retryCount: retryStrategyCount},
   164  	)
   165  	if err != nil {
   166  		return nil, errors.Trace(err)
   167  	}
   168  	return task, nil
   169  }
   170  
   171  // NewEnvironProvisioner returns a new Provisioner for an environment.
   172  // When new machines are added to the state, it allocates instances
   173  // from the environment and allocates them to the new machines.
   174  func NewEnvironProvisioner(st *apiprovisioner.State, agentConfig agent.Config, environ environs.Environ) (Provisioner, error) {
   175  	p := &environProvisioner{
   176  		provisioner: provisioner{
   177  			st:          st,
   178  			agentConfig: agentConfig,
   179  			toolsFinder: getToolsFinder(st),
   180  		},
   181  		environ: environ,
   182  	}
   183  	p.Provisioner = p
   184  	p.broker = environ
   185  	logger.Tracef("Starting environ provisioner for %q", p.agentConfig.Tag())
   186  
   187  	err := catacomb.Invoke(catacomb.Plan{
   188  		Site: &p.catacomb,
   189  		Work: p.loop,
   190  	})
   191  	if err != nil {
   192  		return nil, errors.Trace(err)
   193  	}
   194  	return p, nil
   195  }
   196  
   197  func (p *environProvisioner) loop() error {
   198  	// TODO(mjs channeling axw) - It would be better if there were
   199  	// APIs to watch and fetch provisioner specific config instead of
   200  	// watcher for all changes to model config. This would avoid the
   201  	// need for a full model config.
   202  	var modelConfigChanges <-chan struct{}
   203  	modelWatcher, err := p.st.WatchForModelConfigChanges()
   204  	if err != nil {
   205  		return loggedErrorStack(errors.Trace(err))
   206  	}
   207  	if err := p.catacomb.Add(modelWatcher); err != nil {
   208  		return errors.Trace(err)
   209  	}
   210  	modelConfigChanges = modelWatcher.Changes()
   211  
   212  	modelConfig := p.environ.Config()
   213  	p.configObserver.notify(modelConfig)
   214  	harvestMode := modelConfig.ProvisionerHarvestMode()
   215  	task, err := p.getStartTask(harvestMode)
   216  	if err != nil {
   217  		return loggedErrorStack(errors.Trace(err))
   218  	}
   219  	if err := p.catacomb.Add(task); err != nil {
   220  		return errors.Trace(err)
   221  	}
   222  
   223  	for {
   224  		select {
   225  		case <-p.catacomb.Dying():
   226  			return p.catacomb.ErrDying()
   227  		case _, ok := <-modelConfigChanges:
   228  			if !ok {
   229  				return errors.New("model configuration watcher closed")
   230  			}
   231  			modelConfig, err := p.st.ModelConfig()
   232  			if err != nil {
   233  				return errors.Annotate(err, "cannot load model configuration")
   234  			}
   235  			if err := p.setConfig(modelConfig); err != nil {
   236  				return errors.Annotate(err, "loaded invalid model configuration")
   237  			}
   238  			task.SetHarvestMode(modelConfig.ProvisionerHarvestMode())
   239  		}
   240  	}
   241  }
   242  
   243  func (p *environProvisioner) getMachineWatcher() (watcher.StringsWatcher, error) {
   244  	return p.st.WatchModelMachines()
   245  }
   246  
   247  func (p *environProvisioner) getRetryWatcher() (watcher.NotifyWatcher, error) {
   248  	return p.st.WatchMachineErrorRetry()
   249  }
   250  
   251  // setConfig updates the environment configuration and notifies
   252  // the config observer.
   253  func (p *environProvisioner) setConfig(modelConfig *config.Config) error {
   254  	if err := p.environ.SetConfig(modelConfig); err != nil {
   255  		return err
   256  	}
   257  	p.configObserver.notify(modelConfig)
   258  	return nil
   259  }
   260  
   261  // NewContainerProvisioner returns a new Provisioner. When new machines
   262  // are added to the state, it allocates instances from the environment
   263  // and allocates them to the new machines.
   264  func NewContainerProvisioner(
   265  	containerType instance.ContainerType,
   266  	st *apiprovisioner.State,
   267  	agentConfig agent.Config,
   268  	broker environs.InstanceBroker,
   269  	toolsFinder ToolsFinder,
   270  ) (Provisioner, error) {
   271  
   272  	p := &containerProvisioner{
   273  		provisioner: provisioner{
   274  			st:          st,
   275  			agentConfig: agentConfig,
   276  			broker:      broker,
   277  			toolsFinder: toolsFinder,
   278  		},
   279  		containerType: containerType,
   280  	}
   281  	p.Provisioner = p
   282  	logger.Tracef("Starting %s provisioner for %q", p.containerType, p.agentConfig.Tag())
   283  
   284  	err := catacomb.Invoke(catacomb.Plan{
   285  		Site: &p.catacomb,
   286  		Work: p.loop,
   287  	})
   288  	if err != nil {
   289  		return nil, errors.Trace(err)
   290  	}
   291  	return p, nil
   292  }
   293  
   294  func (p *containerProvisioner) loop() error {
   295  	modelWatcher, err := p.st.WatchForModelConfigChanges()
   296  	if err != nil {
   297  		return errors.Trace(err)
   298  	}
   299  	if err := p.catacomb.Add(modelWatcher); err != nil {
   300  		return errors.Trace(err)
   301  	}
   302  
   303  	modelConfig, err := p.st.ModelConfig()
   304  	if err != nil {
   305  		return err
   306  	}
   307  	p.configObserver.notify(modelConfig)
   308  	harvestMode := modelConfig.ProvisionerHarvestMode()
   309  
   310  	task, err := p.getStartTask(harvestMode)
   311  	if err != nil {
   312  		return err
   313  	}
   314  	if err := p.catacomb.Add(task); err != nil {
   315  		return errors.Trace(err)
   316  	}
   317  
   318  	for {
   319  		select {
   320  		case <-p.catacomb.Dying():
   321  			return p.catacomb.ErrDying()
   322  		case _, ok := <-modelWatcher.Changes():
   323  			if !ok {
   324  				return errors.New("model configuration watch closed")
   325  			}
   326  			modelConfig, err := p.st.ModelConfig()
   327  			if err != nil {
   328  				return errors.Annotate(err, "cannot load model configuration")
   329  			}
   330  			p.configObserver.notify(modelConfig)
   331  			task.SetHarvestMode(modelConfig.ProvisionerHarvestMode())
   332  		}
   333  	}
   334  }
   335  
   336  func (p *containerProvisioner) getMachine() (*apiprovisioner.Machine, error) {
   337  	if p.machine == nil {
   338  		tag := p.agentConfig.Tag()
   339  		machineTag, ok := tag.(names.MachineTag)
   340  		if !ok {
   341  			return nil, errors.Errorf("expected names.MachineTag, got %T", tag)
   342  		}
   343  		var err error
   344  		if p.machine, err = p.st.Machine(machineTag); err != nil {
   345  			logger.Errorf("%s is not in state", machineTag)
   346  			return nil, err
   347  		}
   348  	}
   349  	return p.machine, nil
   350  }
   351  
   352  func (p *containerProvisioner) getMachineWatcher() (watcher.StringsWatcher, error) {
   353  	machine, err := p.getMachine()
   354  	if err != nil {
   355  		return nil, err
   356  	}
   357  	return machine.WatchContainers(p.containerType)
   358  }
   359  
   360  func (p *containerProvisioner) getRetryWatcher() (watcher.NotifyWatcher, error) {
   361  	return nil, errors.NotImplementedf("getRetryWatcher")
   362  }