github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/provisioner/provisioner_task.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  	"fmt"
     8  	"regexp"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	"github.com/juju/utils"
    14  
    15  	apiprovisioner "github.com/juju/juju/api/provisioner"
    16  	"github.com/juju/juju/apiserver/common/networkingcommon"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/cloudconfig/instancecfg"
    19  	"github.com/juju/juju/constraints"
    20  	"github.com/juju/juju/controller/authentication"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/environs/config"
    23  	"github.com/juju/juju/environs/imagemetadata"
    24  	"github.com/juju/juju/environs/simplestreams"
    25  	"github.com/juju/juju/instance"
    26  	"github.com/juju/juju/network"
    27  	"github.com/juju/juju/status"
    28  	"github.com/juju/juju/storage"
    29  	coretools "github.com/juju/juju/tools"
    30  	jujuversion "github.com/juju/juju/version"
    31  	"github.com/juju/juju/watcher"
    32  	"github.com/juju/juju/worker"
    33  	"github.com/juju/juju/worker/catacomb"
    34  	"github.com/juju/version"
    35  )
    36  
    37  type ProvisionerTask interface {
    38  	worker.Worker
    39  
    40  	// SetHarvestMode sets a flag to indicate how the provisioner task
    41  	// should harvest machines. See config.HarvestMode for
    42  	// documentation of behavior.
    43  	SetHarvestMode(mode config.HarvestMode)
    44  }
    45  
    46  type MachineGetter interface {
    47  	Machine(names.MachineTag) (*apiprovisioner.Machine, error)
    48  	MachinesWithTransientErrors() ([]*apiprovisioner.Machine, []params.StatusResult, error)
    49  }
    50  
    51  // ToolsFinder is an interface used for finding tools to run on
    52  // provisioned instances.
    53  type ToolsFinder interface {
    54  	// FindTools returns a list of tools matching the specified
    55  	// version, series, and architecture. If arch is empty, the
    56  	// implementation is expected to use a well documented default.
    57  	FindTools(version version.Number, series string, arch string) (coretools.List, error)
    58  }
    59  
    60  var _ MachineGetter = (*apiprovisioner.State)(nil)
    61  var _ ToolsFinder = (*apiprovisioner.State)(nil)
    62  
    63  func NewProvisionerTask(
    64  	machineTag names.MachineTag,
    65  	harvestMode config.HarvestMode,
    66  	machineGetter MachineGetter,
    67  	toolsFinder ToolsFinder,
    68  	machineWatcher watcher.StringsWatcher,
    69  	retryWatcher watcher.NotifyWatcher,
    70  	broker environs.InstanceBroker,
    71  	auth authentication.AuthenticationProvider,
    72  	imageStream string,
    73  	secureServerConnection bool,
    74  	retryStartInstanceStrategy RetryStrategy,
    75  ) (ProvisionerTask, error) {
    76  	machineChanges := machineWatcher.Changes()
    77  	workers := []worker.Worker{machineWatcher}
    78  	var retryChanges watcher.NotifyChannel
    79  	if retryWatcher != nil {
    80  		retryChanges = retryWatcher.Changes()
    81  		workers = append(workers, retryWatcher)
    82  	}
    83  	task := &provisionerTask{
    84  		machineTag:                 machineTag,
    85  		machineGetter:              machineGetter,
    86  		toolsFinder:                toolsFinder,
    87  		machineChanges:             machineChanges,
    88  		retryChanges:               retryChanges,
    89  		broker:                     broker,
    90  		auth:                       auth,
    91  		harvestMode:                harvestMode,
    92  		harvestModeChan:            make(chan config.HarvestMode, 1),
    93  		machines:                   make(map[string]*apiprovisioner.Machine),
    94  		imageStream:                imageStream,
    95  		secureServerConnection:     secureServerConnection,
    96  		retryStartInstanceStrategy: retryStartInstanceStrategy,
    97  	}
    98  	err := catacomb.Invoke(catacomb.Plan{
    99  		Site: &task.catacomb,
   100  		Work: task.loop,
   101  		Init: workers,
   102  	})
   103  	if err != nil {
   104  		return nil, errors.Trace(err)
   105  	}
   106  	return task, nil
   107  }
   108  
   109  type provisionerTask struct {
   110  	machineTag                 names.MachineTag
   111  	machineGetter              MachineGetter
   112  	toolsFinder                ToolsFinder
   113  	machineChanges             watcher.StringsChannel
   114  	retryChanges               watcher.NotifyChannel
   115  	broker                     environs.InstanceBroker
   116  	catacomb                   catacomb.Catacomb
   117  	auth                       authentication.AuthenticationProvider
   118  	imageStream                string
   119  	secureServerConnection     bool
   120  	harvestMode                config.HarvestMode
   121  	harvestModeChan            chan config.HarvestMode
   122  	retryStartInstanceStrategy RetryStrategy
   123  	// instance id -> instance
   124  	instances map[instance.Id]instance.Instance
   125  	// machine id -> machine
   126  	machines map[string]*apiprovisioner.Machine
   127  }
   128  
   129  // Kill implements worker.Worker.Kill.
   130  func (task *provisionerTask) Kill() {
   131  	task.catacomb.Kill(nil)
   132  }
   133  
   134  // Wait implements worker.Worker.Wait.
   135  func (task *provisionerTask) Wait() error {
   136  	return task.catacomb.Wait()
   137  }
   138  
   139  func (task *provisionerTask) loop() error {
   140  
   141  	// Don't allow the harvesting mode to change until we have read at
   142  	// least one set of changes, which will populate the task.machines
   143  	// map. Otherwise we will potentially see all legitimate instances
   144  	// as unknown.
   145  	var harvestModeChan chan config.HarvestMode
   146  
   147  	// When the watcher is started, it will have the initial changes be all
   148  	// the machines that are relevant. Also, since this is available straight
   149  	// away, we know there will be some changes right off the bat.
   150  	for {
   151  		select {
   152  		case <-task.catacomb.Dying():
   153  			logger.Infof("Shutting down provisioner task %s", task.machineTag)
   154  			return task.catacomb.ErrDying()
   155  		case ids, ok := <-task.machineChanges:
   156  			if !ok {
   157  				return errors.New("machine watcher closed channel")
   158  			}
   159  			if err := task.processMachines(ids); err != nil {
   160  				return errors.Annotate(err, "failed to process updated machines")
   161  			}
   162  
   163  			// We've seen a set of changes. Enable modification of
   164  			// harvesting mode.
   165  			harvestModeChan = task.harvestModeChan
   166  		case harvestMode := <-harvestModeChan:
   167  			if harvestMode == task.harvestMode {
   168  				break
   169  			}
   170  			logger.Infof("harvesting mode changed to %s", harvestMode)
   171  			task.harvestMode = harvestMode
   172  			if harvestMode.HarvestUnknown() {
   173  				logger.Infof("harvesting unknown machines")
   174  				if err := task.processMachines(nil); err != nil {
   175  					return errors.Annotate(err, "failed to process machines after safe mode disabled")
   176  				}
   177  			}
   178  		case <-task.retryChanges:
   179  			if err := task.processMachinesWithTransientErrors(); err != nil {
   180  				return errors.Annotate(err, "failed to process machines with transient errors")
   181  			}
   182  		}
   183  	}
   184  }
   185  
   186  // SetHarvestMode implements ProvisionerTask.SetHarvestMode().
   187  func (task *provisionerTask) SetHarvestMode(mode config.HarvestMode) {
   188  	select {
   189  	case task.harvestModeChan <- mode:
   190  	case <-task.catacomb.Dying():
   191  	}
   192  }
   193  
   194  func (task *provisionerTask) processMachinesWithTransientErrors() error {
   195  	machines, statusResults, err := task.machineGetter.MachinesWithTransientErrors()
   196  	if err != nil {
   197  		return nil
   198  	}
   199  	logger.Tracef("processMachinesWithTransientErrors(%v)", statusResults)
   200  	var pending []*apiprovisioner.Machine
   201  	for i, statusResult := range statusResults {
   202  		if statusResult.Error != nil {
   203  			logger.Errorf("cannot retry provisioning of machine %q: %v", statusResult.Id, statusResult.Error)
   204  			continue
   205  		}
   206  		machine := machines[i]
   207  		if err := machine.SetStatus(status.StatusPending, "", nil); err != nil {
   208  			logger.Errorf("cannot reset status of machine %q: %v", statusResult.Id, err)
   209  			continue
   210  		}
   211  		task.machines[machine.Tag().String()] = machine
   212  		pending = append(pending, machine)
   213  	}
   214  	return task.startMachines(pending)
   215  }
   216  
   217  func (task *provisionerTask) processMachines(ids []string) error {
   218  	logger.Tracef("processMachines(%v)", ids)
   219  
   220  	// Populate the tasks maps of current instances and machines.
   221  	if err := task.populateMachineMaps(ids); err != nil {
   222  		return err
   223  	}
   224  
   225  	// Find machines without an instance id or that are dead
   226  	pending, dead, maintain, err := task.pendingOrDeadOrMaintain(ids)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	// Stop all machines that are dead
   232  	stopping := task.instancesForMachines(dead)
   233  
   234  	// Find running instances that have no machines associated
   235  	unknown, err := task.findUnknownInstances(stopping)
   236  	if err != nil {
   237  		return err
   238  	}
   239  	if !task.harvestMode.HarvestUnknown() {
   240  		logger.Infof(
   241  			"%s is set to %s; unknown instances not stopped %v",
   242  			config.ProvisionerHarvestModeKey,
   243  			task.harvestMode.String(),
   244  			instanceIds(unknown),
   245  		)
   246  		unknown = nil
   247  	}
   248  	if task.harvestMode.HarvestNone() || !task.harvestMode.HarvestDestroyed() {
   249  		logger.Infof(
   250  			`%s is set to "%s"; will not harvest %s`,
   251  			config.ProvisionerHarvestModeKey,
   252  			task.harvestMode.String(),
   253  			instanceIds(stopping),
   254  		)
   255  		stopping = nil
   256  	}
   257  
   258  	if len(stopping) > 0 {
   259  		logger.Infof("stopping known instances %v", stopping)
   260  	}
   261  	if len(unknown) > 0 {
   262  		logger.Infof("stopping unknown instances %v", instanceIds(unknown))
   263  	}
   264  	// It's important that we stop unknown instances before starting
   265  	// pending ones, because if we start an instance and then fail to
   266  	// set its InstanceId on the machine we don't want to start a new
   267  	// instance for the same machine ID.
   268  	if err := task.stopInstances(append(stopping, unknown...)); err != nil {
   269  		return err
   270  	}
   271  
   272  	// Remove any dead machines from state.
   273  	for _, machine := range dead {
   274  		logger.Infof("removing dead machine %q", machine)
   275  		if err := machine.Remove(); err != nil {
   276  			logger.Errorf("failed to remove dead machine %q", machine)
   277  		}
   278  		delete(task.machines, machine.Id())
   279  	}
   280  
   281  	// Any machines that require maintenance get pinged
   282  	task.maintainMachines(maintain)
   283  
   284  	// Start an instance for the pending ones
   285  	return task.startMachines(pending)
   286  }
   287  
   288  func instanceIds(instances []instance.Instance) []string {
   289  	ids := make([]string, 0, len(instances))
   290  	for _, inst := range instances {
   291  		ids = append(ids, string(inst.Id()))
   292  	}
   293  	return ids
   294  }
   295  
   296  // populateMachineMaps updates task.instances. Also updates
   297  // task.machines map if a list of IDs is given.
   298  func (task *provisionerTask) populateMachineMaps(ids []string) error {
   299  	task.instances = make(map[instance.Id]instance.Instance)
   300  
   301  	instances, err := task.broker.AllInstances()
   302  	if err != nil {
   303  		return errors.Annotate(err, "failed to get all instances from broker")
   304  	}
   305  	for _, i := range instances {
   306  		task.instances[i.Id()] = i
   307  	}
   308  
   309  	// Update the machines map with new data for each of the machines in the
   310  	// change list.
   311  	// TODO(thumper): update for API server later to get all machines in one go.
   312  	for _, id := range ids {
   313  		machineTag := names.NewMachineTag(id)
   314  		machine, err := task.machineGetter.Machine(machineTag)
   315  		switch {
   316  		case params.IsCodeNotFoundOrCodeUnauthorized(err):
   317  			logger.Debugf("machine %q not found in state", id)
   318  			delete(task.machines, id)
   319  		case err == nil:
   320  			task.machines[id] = machine
   321  		default:
   322  			return errors.Annotatef(err, "failed to get machine %v", id)
   323  		}
   324  	}
   325  	return nil
   326  }
   327  
   328  // pendingOrDead looks up machines with ids and returns those that do not
   329  // have an instance id assigned yet, and also those that are dead.
   330  func (task *provisionerTask) pendingOrDeadOrMaintain(ids []string) (pending, dead, maintain []*apiprovisioner.Machine, err error) {
   331  	for _, id := range ids {
   332  		machine, found := task.machines[id]
   333  		if !found {
   334  			logger.Infof("machine %q not found", id)
   335  			continue
   336  		}
   337  		var classification MachineClassification
   338  		classification, err = classifyMachine(machine)
   339  		if err != nil {
   340  			return // return the error
   341  		}
   342  		switch classification {
   343  		case Pending:
   344  			pending = append(pending, machine)
   345  		case Dead:
   346  			dead = append(dead, machine)
   347  		case Maintain:
   348  			maintain = append(maintain, machine)
   349  		}
   350  	}
   351  	logger.Tracef("pending machines: %v", pending)
   352  	logger.Tracef("dead machines: %v", dead)
   353  	return
   354  }
   355  
   356  type ClassifiableMachine interface {
   357  	Life() params.Life
   358  	InstanceId() (instance.Id, error)
   359  	EnsureDead() error
   360  	Status() (status.Status, string, error)
   361  	Id() string
   362  }
   363  
   364  type MachineClassification string
   365  
   366  const (
   367  	None     MachineClassification = "none"
   368  	Pending  MachineClassification = "Pending"
   369  	Dead     MachineClassification = "Dead"
   370  	Maintain MachineClassification = "Maintain"
   371  )
   372  
   373  func classifyMachine(machine ClassifiableMachine) (
   374  	MachineClassification, error) {
   375  	switch machine.Life() {
   376  	case params.Dying:
   377  		if _, err := machine.InstanceId(); err == nil {
   378  			return None, nil
   379  		} else if !params.IsCodeNotProvisioned(err) {
   380  			return None, errors.Annotatef(err, "failed to load dying machine id:%s, details:%v", machine.Id(), machine)
   381  		}
   382  		logger.Infof("killing dying, unprovisioned machine %q", machine)
   383  		if err := machine.EnsureDead(); err != nil {
   384  			return None, errors.Annotatef(err, "failed to ensure machine dead id:%s, details:%v", machine.Id(), machine)
   385  		}
   386  		fallthrough
   387  	case params.Dead:
   388  		return Dead, nil
   389  	}
   390  	if instId, err := machine.InstanceId(); err != nil {
   391  		if !params.IsCodeNotProvisioned(err) {
   392  			return None, errors.Annotatef(err, "failed to load machine id:%s, details:%v", machine.Id(), machine)
   393  		}
   394  		machineStatus, _, err := machine.Status()
   395  		if err != nil {
   396  			logger.Infof("cannot get machine id:%s, details:%v, err:%v", machine.Id(), machine, err)
   397  			return None, nil
   398  		}
   399  		if machineStatus == status.StatusPending {
   400  			logger.Infof("found machine pending provisioning id:%s, details:%v", machine.Id(), machine)
   401  			return Pending, nil
   402  		}
   403  	} else {
   404  		logger.Infof("machine %s already started as instance %q", machine.Id(), instId)
   405  		if err != nil {
   406  			logger.Infof("Error fetching provisioning info")
   407  		} else {
   408  			isLxc := regexp.MustCompile(`\d+/lxc/\d+`)
   409  			isKvm := regexp.MustCompile(`\d+/kvm/\d+`)
   410  			if isLxc.MatchString(machine.Id()) || isKvm.MatchString(machine.Id()) {
   411  				return Maintain, nil
   412  			}
   413  		}
   414  	}
   415  	return None, nil
   416  }
   417  
   418  // findUnknownInstances finds instances which are not associated with a machine.
   419  func (task *provisionerTask) findUnknownInstances(stopping []instance.Instance) ([]instance.Instance, error) {
   420  	// Make a copy of the instances we know about.
   421  	instances := make(map[instance.Id]instance.Instance)
   422  	for k, v := range task.instances {
   423  		instances[k] = v
   424  	}
   425  
   426  	for _, m := range task.machines {
   427  		instId, err := m.InstanceId()
   428  		switch {
   429  		case err == nil:
   430  			delete(instances, instId)
   431  		case params.IsCodeNotProvisioned(err):
   432  		case params.IsCodeNotFoundOrCodeUnauthorized(err):
   433  		default:
   434  			return nil, err
   435  		}
   436  	}
   437  	// Now remove all those instances that we are stopping already as we
   438  	// know about those and don't want to include them in the unknown list.
   439  	for _, inst := range stopping {
   440  		delete(instances, inst.Id())
   441  	}
   442  	var unknown []instance.Instance
   443  	for _, inst := range instances {
   444  		unknown = append(unknown, inst)
   445  	}
   446  	return unknown, nil
   447  }
   448  
   449  // instancesForMachines returns a list of instance.Instance that represent
   450  // the list of machines running in the provider. Missing machines are
   451  // omitted from the list.
   452  func (task *provisionerTask) instancesForMachines(machines []*apiprovisioner.Machine) []instance.Instance {
   453  	var instances []instance.Instance
   454  	for _, machine := range machines {
   455  		instId, err := machine.InstanceId()
   456  		if err == nil {
   457  			instance, found := task.instances[instId]
   458  			// If the instance is not found we can't stop it.
   459  			if found {
   460  				instances = append(instances, instance)
   461  			}
   462  		}
   463  	}
   464  	return instances
   465  }
   466  
   467  func (task *provisionerTask) stopInstances(instances []instance.Instance) error {
   468  	// Although calling StopInstance with an empty slice should produce no change in the
   469  	// provider, environs like dummy do not consider this a noop.
   470  	if len(instances) == 0 {
   471  		return nil
   472  	}
   473  	ids := make([]instance.Id, len(instances))
   474  	for i, inst := range instances {
   475  		ids[i] = inst.Id()
   476  	}
   477  	if err := task.broker.StopInstances(ids...); err != nil {
   478  		return errors.Annotate(err, "broker failed to stop instances")
   479  	}
   480  	return nil
   481  }
   482  
   483  func (task *provisionerTask) constructInstanceConfig(
   484  	machine *apiprovisioner.Machine,
   485  	auth authentication.AuthenticationProvider,
   486  	pInfo *params.ProvisioningInfo,
   487  ) (*instancecfg.InstanceConfig, error) {
   488  
   489  	stateInfo, apiInfo, err := auth.SetupAuthentication(machine)
   490  	if err != nil {
   491  		return nil, errors.Annotate(err, "failed to setup authentication")
   492  	}
   493  
   494  	// Generated a nonce for the new instance, with the format: "machine-#:UUID".
   495  	// The first part is a badge, specifying the tag of the machine the provisioner
   496  	// is running on, while the second part is a random UUID.
   497  	uuid, err := utils.NewUUID()
   498  	if err != nil {
   499  		return nil, errors.Annotate(err, "failed to generate a nonce for machine "+machine.Id())
   500  	}
   501  
   502  	publicKey, err := simplestreams.UserPublicSigningKey()
   503  	if err != nil {
   504  		return nil, err
   505  	}
   506  	nonce := fmt.Sprintf("%s:%s", task.machineTag, uuid)
   507  	return instancecfg.NewInstanceConfig(
   508  		machine.Id(),
   509  		nonce,
   510  		task.imageStream,
   511  		pInfo.Series,
   512  		publicKey,
   513  		task.secureServerConnection,
   514  		stateInfo,
   515  		apiInfo,
   516  	)
   517  }
   518  
   519  func constructStartInstanceParams(
   520  	machine *apiprovisioner.Machine,
   521  	instanceConfig *instancecfg.InstanceConfig,
   522  	provisioningInfo *params.ProvisioningInfo,
   523  	possibleTools coretools.List,
   524  ) (environs.StartInstanceParams, error) {
   525  
   526  	volumes := make([]storage.VolumeParams, len(provisioningInfo.Volumes))
   527  	for i, v := range provisioningInfo.Volumes {
   528  		volumeTag, err := names.ParseVolumeTag(v.VolumeTag)
   529  		if err != nil {
   530  			return environs.StartInstanceParams{}, errors.Trace(err)
   531  		}
   532  		if v.Attachment == nil {
   533  			return environs.StartInstanceParams{}, errors.Errorf("volume params missing attachment")
   534  		}
   535  		machineTag, err := names.ParseMachineTag(v.Attachment.MachineTag)
   536  		if err != nil {
   537  			return environs.StartInstanceParams{}, errors.Trace(err)
   538  		}
   539  		if machineTag != machine.Tag() {
   540  			return environs.StartInstanceParams{}, errors.Errorf("volume attachment params has invalid machine tag")
   541  		}
   542  		if v.Attachment.InstanceId != "" {
   543  			return environs.StartInstanceParams{}, errors.Errorf("volume attachment params specifies instance ID")
   544  		}
   545  		volumes[i] = storage.VolumeParams{
   546  			volumeTag,
   547  			v.Size,
   548  			storage.ProviderType(v.Provider),
   549  			v.Attributes,
   550  			v.Tags,
   551  			&storage.VolumeAttachmentParams{
   552  				AttachmentParams: storage.AttachmentParams{
   553  					Machine:  machineTag,
   554  					ReadOnly: v.Attachment.ReadOnly,
   555  				},
   556  				Volume: volumeTag,
   557  			},
   558  		}
   559  	}
   560  
   561  	var subnetsToZones map[network.Id][]string
   562  	if provisioningInfo.SubnetsToZones != nil {
   563  		// Convert subnet provider ids from string to network.Id.
   564  		subnetsToZones = make(map[network.Id][]string, len(provisioningInfo.SubnetsToZones))
   565  		for providerId, zones := range provisioningInfo.SubnetsToZones {
   566  			subnetsToZones[network.Id(providerId)] = zones
   567  		}
   568  	}
   569  
   570  	var endpointBindings map[string]network.Id
   571  	if len(provisioningInfo.EndpointBindings) != 0 {
   572  		endpointBindings = make(map[string]network.Id)
   573  		for endpoint, space := range provisioningInfo.EndpointBindings {
   574  			endpointBindings[endpoint] = network.Id(space)
   575  		}
   576  	}
   577  	possibleImageMetadata := make([]*imagemetadata.ImageMetadata, len(provisioningInfo.ImageMetadata))
   578  	for i, metadata := range provisioningInfo.ImageMetadata {
   579  		possibleImageMetadata[i] = &imagemetadata.ImageMetadata{
   580  			Id:          metadata.ImageId,
   581  			Arch:        metadata.Arch,
   582  			RegionAlias: metadata.Region,
   583  			RegionName:  metadata.Region,
   584  			Storage:     metadata.RootStorageType,
   585  			Stream:      metadata.Stream,
   586  			VirtType:    metadata.VirtType,
   587  			Version:     metadata.Version,
   588  		}
   589  	}
   590  
   591  	return environs.StartInstanceParams{
   592  		Constraints:       provisioningInfo.Constraints,
   593  		Tools:             possibleTools,
   594  		InstanceConfig:    instanceConfig,
   595  		Placement:         provisioningInfo.Placement,
   596  		DistributionGroup: machine.DistributionGroup,
   597  		Volumes:           volumes,
   598  		SubnetsToZones:    subnetsToZones,
   599  		EndpointBindings:  endpointBindings,
   600  		ImageMetadata:     possibleImageMetadata,
   601  		StatusCallback:    machine.SetInstanceStatus,
   602  	}, nil
   603  }
   604  
   605  func (task *provisionerTask) maintainMachines(machines []*apiprovisioner.Machine) error {
   606  	for _, m := range machines {
   607  		logger.Infof("maintainMachines: %v", m)
   608  		startInstanceParams := environs.StartInstanceParams{}
   609  		startInstanceParams.InstanceConfig = &instancecfg.InstanceConfig{}
   610  		startInstanceParams.InstanceConfig.MachineId = m.Id()
   611  		if err := task.broker.MaintainInstance(startInstanceParams); err != nil {
   612  			return errors.Annotatef(err, "cannot maintain machine %v", m)
   613  		}
   614  	}
   615  	return nil
   616  }
   617  
   618  func (task *provisionerTask) startMachines(machines []*apiprovisioner.Machine) error {
   619  	for _, m := range machines {
   620  
   621  		pInfo, err := m.ProvisioningInfo()
   622  		if err != nil {
   623  			return task.setErrorStatus("fetching provisioning info for machine %q: %v", m, err)
   624  		}
   625  
   626  		instanceCfg, err := task.constructInstanceConfig(m, task.auth, pInfo)
   627  		if err != nil {
   628  			return task.setErrorStatus("creating instance config for machine %q: %v", m, err)
   629  		}
   630  
   631  		assocProvInfoAndMachCfg(pInfo, instanceCfg)
   632  
   633  		var arch string
   634  		if pInfo.Constraints.Arch != nil {
   635  			arch = *pInfo.Constraints.Arch
   636  		}
   637  
   638  		possibleTools, err := task.toolsFinder.FindTools(
   639  			jujuversion.Current,
   640  			pInfo.Series,
   641  			arch,
   642  		)
   643  		if err != nil {
   644  			return task.setErrorStatus("cannot find tools for machine %q: %v", m, err)
   645  		}
   646  
   647  		startInstanceParams, err := constructStartInstanceParams(
   648  			m,
   649  			instanceCfg,
   650  			pInfo,
   651  			possibleTools,
   652  		)
   653  		if err != nil {
   654  			return task.setErrorStatus("cannot construct params for machine %q: %v", m, err)
   655  		}
   656  
   657  		if err := task.startMachine(m, pInfo, startInstanceParams); err != nil {
   658  			return errors.Annotatef(err, "cannot start machine %v", m)
   659  		}
   660  	}
   661  	return nil
   662  }
   663  
   664  func (task *provisionerTask) setErrorStatus(message string, machine *apiprovisioner.Machine, err error) error {
   665  	logger.Errorf(message, machine, err)
   666  	if err1 := machine.SetStatus(status.StatusError, err.Error(), nil); err1 != nil {
   667  		// Something is wrong with this machine, better report it back.
   668  		return errors.Annotatef(err1, "cannot set error status for machine %q", machine)
   669  	}
   670  	return nil
   671  }
   672  
   673  func (task *provisionerTask) startMachine(
   674  	machine *apiprovisioner.Machine,
   675  	provisioningInfo *params.ProvisioningInfo,
   676  	startInstanceParams environs.StartInstanceParams,
   677  ) error {
   678  
   679  	result, err := task.broker.StartInstance(startInstanceParams)
   680  	if err != nil {
   681  		if !instance.IsRetryableCreationError(errors.Cause(err)) {
   682  			// Set the state to error, so the machine will be skipped next
   683  			// time until the error is resolved, but don't return an
   684  			// error; just keep going with the other machines.
   685  			return task.setErrorStatus("cannot start instance for machine %q: %v", machine, err)
   686  		}
   687  		logger.Infof("retryable error received on start instance: %v", err)
   688  		for count := task.retryStartInstanceStrategy.retryCount; count > 0; count-- {
   689  			if task.retryStartInstanceStrategy.retryDelay > 0 {
   690  				select {
   691  				case <-task.catacomb.Dying():
   692  					return task.catacomb.ErrDying()
   693  				case <-time.After(task.retryStartInstanceStrategy.retryDelay):
   694  				}
   695  
   696  			}
   697  			result, err = task.broker.StartInstance(startInstanceParams)
   698  			if err == nil {
   699  				break
   700  			}
   701  			// If this was the last attempt and an error was received, set the error
   702  			// status on the machine.
   703  			if count == 1 {
   704  				return task.setErrorStatus("cannot start instance for machine %q: %v", machine, err)
   705  			}
   706  		}
   707  	}
   708  
   709  	inst := result.Instance
   710  	hardware := result.Hardware
   711  	nonce := startInstanceParams.InstanceConfig.MachineNonce
   712  	networkConfig := networkingcommon.NetworkConfigFromInterfaceInfo(result.NetworkInfo)
   713  	volumes := volumesToApiserver(result.Volumes)
   714  	volumeAttachments := volumeAttachmentsToApiserver(result.VolumeAttachments)
   715  
   716  	err = machine.SetInstanceInfo(inst.Id(), nonce, hardware, networkConfig, volumes, volumeAttachments)
   717  	if err == nil {
   718  		logger.Infof(
   719  			"started machine %s as instance %s with hardware %q, network config %+v, volumes %v, volume attachments %v, subnets to zones %v",
   720  			machine, inst.Id(), hardware,
   721  			networkConfig,
   722  			volumes, volumeAttachments,
   723  			startInstanceParams.SubnetsToZones,
   724  		)
   725  		return nil
   726  	}
   727  	// We need to stop the instance right away here, set error status and go on.
   728  	task.setErrorStatus("cannot register instance for machine %v: %v", machine, err)
   729  	if err := task.broker.StopInstances(inst.Id()); err != nil {
   730  		// We cannot even stop the instance, log the error and quit.
   731  		return errors.Annotatef(err, "cannot stop instance %q for machine %v", inst.Id(), machine)
   732  	}
   733  	return nil
   734  }
   735  
   736  type provisioningInfo struct {
   737  	Constraints    constraints.Value
   738  	Series         string
   739  	Placement      string
   740  	InstanceConfig *instancecfg.InstanceConfig
   741  	SubnetsToZones map[string][]string
   742  }
   743  
   744  func assocProvInfoAndMachCfg(
   745  	provInfo *params.ProvisioningInfo,
   746  	instanceConfig *instancecfg.InstanceConfig,
   747  ) *provisioningInfo {
   748  
   749  	instanceConfig.Tags = provInfo.Tags
   750  
   751  	if len(provInfo.Jobs) > 0 {
   752  		instanceConfig.Jobs = provInfo.Jobs
   753  	}
   754  
   755  	return &provisioningInfo{
   756  		Constraints:    provInfo.Constraints,
   757  		Series:         provInfo.Series,
   758  		Placement:      provInfo.Placement,
   759  		InstanceConfig: instanceConfig,
   760  		SubnetsToZones: provInfo.SubnetsToZones,
   761  	}
   762  }
   763  
   764  func volumesToApiserver(volumes []storage.Volume) []params.Volume {
   765  	result := make([]params.Volume, len(volumes))
   766  	for i, v := range volumes {
   767  		result[i] = params.Volume{
   768  			v.Tag.String(),
   769  			params.VolumeInfo{
   770  				v.VolumeId,
   771  				v.HardwareId,
   772  				v.Size,
   773  				v.Persistent,
   774  			},
   775  		}
   776  	}
   777  	return result
   778  }
   779  
   780  func volumeAttachmentsToApiserver(attachments []storage.VolumeAttachment) map[string]params.VolumeAttachmentInfo {
   781  	result := make(map[string]params.VolumeAttachmentInfo)
   782  	for _, a := range attachments {
   783  		result[a.Volume.String()] = params.VolumeAttachmentInfo{
   784  			a.DeviceName,
   785  			a.DeviceLink,
   786  			a.BusAddress,
   787  			a.ReadOnly,
   788  		}
   789  	}
   790  	return result
   791  }