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