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 }