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