github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/apiserver/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 "fmt" 8 9 "github.com/juju/errors" 10 "github.com/juju/names" 11 "github.com/juju/utils/set" 12 13 "github.com/juju/juju/apiserver/common" 14 "github.com/juju/juju/apiserver/params" 15 "github.com/juju/juju/constraints" 16 "github.com/juju/juju/container" 17 "github.com/juju/juju/instance" 18 "github.com/juju/juju/state" 19 "github.com/juju/juju/state/multiwatcher" 20 "github.com/juju/juju/state/watcher" 21 "github.com/juju/juju/storage" 22 ) 23 24 func init() { 25 common.RegisterStandardFacade("Provisioner", 0, NewProvisionerAPI) 26 } 27 28 // ProvisionerAPI provides access to the Provisioner API facade. 29 type ProvisionerAPI struct { 30 *common.Remover 31 *common.StatusSetter 32 *common.DeadEnsurer 33 *common.PasswordChanger 34 *common.LifeGetter 35 *common.StateAddresser 36 *common.APIAddresser 37 *common.EnvironWatcher 38 *common.EnvironMachinesWatcher 39 *common.InstanceIdGetter 40 *common.ToolsFinder 41 42 st *state.State 43 resources *common.Resources 44 authorizer common.Authorizer 45 getAuthFunc common.GetAuthFunc 46 } 47 48 // NewProvisionerAPI creates a new server-side ProvisionerAPI facade. 49 func NewProvisionerAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer) (*ProvisionerAPI, error) { 50 if !authorizer.AuthMachineAgent() && !authorizer.AuthEnvironManager() { 51 return nil, common.ErrPerm 52 } 53 getAuthFunc := func() (common.AuthFunc, error) { 54 isEnvironManager := authorizer.AuthEnvironManager() 55 isMachineAgent := authorizer.AuthMachineAgent() 56 authEntityTag := authorizer.GetAuthTag() 57 58 return func(tag names.Tag) bool { 59 if isMachineAgent && tag == authEntityTag { 60 // A machine agent can always access its own machine. 61 return true 62 } 63 switch tag := tag.(type) { 64 case names.MachineTag: 65 parentId := state.ParentId(tag.Id()) 66 if parentId == "" { 67 // All top-level machines are accessible by the 68 // environment manager. 69 return isEnvironManager 70 } 71 // All containers with the authenticated machine as a 72 // parent are accessible by it. 73 // TODO(dfc) sometimes authEntity tag is nil, which is fine because nil is 74 // only equal to nil, but it suggests someone is passing an authorizer 75 // with a nil tag. 76 return isMachineAgent && names.NewMachineTag(parentId) == authEntityTag 77 default: 78 return false 79 } 80 }, nil 81 } 82 env, err := st.Environment() 83 if err != nil { 84 return nil, err 85 } 86 urlGetter := common.NewToolsURLGetter(env.UUID(), st) 87 return &ProvisionerAPI{ 88 Remover: common.NewRemover(st, false, getAuthFunc), 89 StatusSetter: common.NewStatusSetter(st, getAuthFunc), 90 DeadEnsurer: common.NewDeadEnsurer(st, getAuthFunc), 91 PasswordChanger: common.NewPasswordChanger(st, getAuthFunc), 92 LifeGetter: common.NewLifeGetter(st, getAuthFunc), 93 StateAddresser: common.NewStateAddresser(st), 94 APIAddresser: common.NewAPIAddresser(st, resources), 95 EnvironWatcher: common.NewEnvironWatcher(st, resources, authorizer), 96 EnvironMachinesWatcher: common.NewEnvironMachinesWatcher(st, resources, authorizer), 97 InstanceIdGetter: common.NewInstanceIdGetter(st, getAuthFunc), 98 ToolsFinder: common.NewToolsFinder(st, st, urlGetter), 99 st: st, 100 resources: resources, 101 authorizer: authorizer, 102 getAuthFunc: getAuthFunc, 103 }, nil 104 } 105 106 func (p *ProvisionerAPI) getMachine(canAccess common.AuthFunc, tag names.MachineTag) (*state.Machine, error) { 107 if !canAccess(tag) { 108 return nil, common.ErrPerm 109 } 110 entity, err := p.st.FindEntity(tag) 111 if err != nil { 112 return nil, err 113 } 114 // The authorization function guarantees that the tag represents a 115 // machine. 116 return entity.(*state.Machine), nil 117 } 118 119 func (p *ProvisionerAPI) watchOneMachineContainers(arg params.WatchContainer) (params.StringsWatchResult, error) { 120 nothing := params.StringsWatchResult{} 121 canAccess, err := p.getAuthFunc() 122 if err != nil { 123 return nothing, common.ErrPerm 124 } 125 tag, err := names.ParseMachineTag(arg.MachineTag) 126 if err != nil { 127 return nothing, common.ErrPerm 128 } 129 if !canAccess(tag) { 130 return nothing, common.ErrPerm 131 } 132 machine, err := p.st.Machine(tag.Id()) 133 if err != nil { 134 return nothing, err 135 } 136 var watch state.StringsWatcher 137 if arg.ContainerType != "" { 138 watch = machine.WatchContainers(instance.ContainerType(arg.ContainerType)) 139 } else { 140 watch = machine.WatchAllContainers() 141 } 142 // Consume the initial event and forward it to the result. 143 if changes, ok := <-watch.Changes(); ok { 144 return params.StringsWatchResult{ 145 StringsWatcherId: p.resources.Register(watch), 146 Changes: changes, 147 }, nil 148 } 149 return nothing, watcher.EnsureErr(watch) 150 } 151 152 // WatchContainers starts a StringsWatcher to watch containers deployed to 153 // any machine passed in args. 154 func (p *ProvisionerAPI) WatchContainers(args params.WatchContainers) (params.StringsWatchResults, error) { 155 result := params.StringsWatchResults{ 156 Results: make([]params.StringsWatchResult, len(args.Params)), 157 } 158 for i, arg := range args.Params { 159 watcherResult, err := p.watchOneMachineContainers(arg) 160 result.Results[i] = watcherResult 161 result.Results[i].Error = common.ServerError(err) 162 } 163 return result, nil 164 } 165 166 // WatchAllContainers starts a StringsWatcher to watch all containers deployed to 167 // any machine passed in args. 168 func (p *ProvisionerAPI) WatchAllContainers(args params.WatchContainers) (params.StringsWatchResults, error) { 169 return p.WatchContainers(args) 170 } 171 172 // SetSupportedContainers updates the list of containers supported by the machines passed in args. 173 func (p *ProvisionerAPI) SetSupportedContainers(args params.MachineContainersParams) (params.ErrorResults, error) { 174 result := params.ErrorResults{ 175 Results: make([]params.ErrorResult, len(args.Params)), 176 } 177 178 canAccess, err := p.getAuthFunc() 179 if err != nil { 180 return result, err 181 } 182 for i, arg := range args.Params { 183 tag, err := names.ParseMachineTag(arg.MachineTag) 184 if err != nil { 185 result.Results[i].Error = common.ServerError(common.ErrPerm) 186 continue 187 } 188 machine, err := p.getMachine(canAccess, tag) 189 if err != nil { 190 result.Results[i].Error = common.ServerError(err) 191 continue 192 } 193 if len(arg.ContainerTypes) == 0 { 194 err = machine.SupportsNoContainers() 195 } else { 196 err = machine.SetSupportedContainers(arg.ContainerTypes) 197 } 198 if err != nil { 199 result.Results[i].Error = common.ServerError(err) 200 } 201 } 202 return result, nil 203 } 204 205 // ContainerManagerConfig returns information from the environment config that is 206 // needed for configuring the container manager. 207 func (p *ProvisionerAPI) ContainerManagerConfig(args params.ContainerManagerConfigParams) (params.ContainerManagerConfig, error) { 208 var result params.ContainerManagerConfig 209 config, err := p.st.EnvironConfig() 210 if err != nil { 211 return result, err 212 } 213 cfg := make(map[string]string) 214 cfg[container.ConfigName] = container.DefaultNamespace 215 switch args.Type { 216 case instance.LXC: 217 if useLxcClone, ok := config.LXCUseClone(); ok { 218 cfg["use-clone"] = fmt.Sprint(useLxcClone) 219 } 220 if useLxcCloneAufs, ok := config.LXCUseCloneAUFS(); ok { 221 cfg["use-aufs"] = fmt.Sprint(useLxcCloneAufs) 222 } 223 } 224 result.ManagerConfig = cfg 225 return result, nil 226 } 227 228 // ContainerConfig returns information from the environment config that is 229 // needed for container cloud-init. 230 func (p *ProvisionerAPI) ContainerConfig() (params.ContainerConfig, error) { 231 result := params.ContainerConfig{} 232 config, err := p.st.EnvironConfig() 233 if err != nil { 234 return result, err 235 } 236 237 result.UpdateBehavior = ¶ms.UpdateBehavior{ 238 config.EnableOSRefreshUpdate(), 239 config.EnableOSUpgrade(), 240 } 241 result.ProviderType = config.Type() 242 result.AuthorizedKeys = config.AuthorizedKeys() 243 result.SSLHostnameVerification = config.SSLHostnameVerification() 244 result.Proxy = config.ProxySettings() 245 result.AptProxy = config.AptProxySettings() 246 result.PreferIPv6 = config.PreferIPv6() 247 248 return result, nil 249 } 250 251 // Status returns the status of each given machine entity. 252 func (p *ProvisionerAPI) Status(args params.Entities) (params.StatusResults, error) { 253 result := params.StatusResults{ 254 Results: make([]params.StatusResult, len(args.Entities)), 255 } 256 canAccess, err := p.getAuthFunc() 257 if err != nil { 258 return result, err 259 } 260 for i, entity := range args.Entities { 261 tag, err := names.ParseMachineTag(entity.Tag) 262 if err != nil { 263 result.Results[i].Error = common.ServerError(common.ErrPerm) 264 continue 265 } 266 machine, err := p.getMachine(canAccess, tag) 267 if err == nil { 268 r := &result.Results[i] 269 var st state.Status 270 st, r.Info, r.Data, err = machine.Status() 271 r.Status = params.Status(st) 272 273 } 274 result.Results[i].Error = common.ServerError(err) 275 } 276 return result, nil 277 } 278 279 // MachinesWithTransientErrors returns status data for machines with provisioning 280 // errors which are transient. 281 func (p *ProvisionerAPI) MachinesWithTransientErrors() (params.StatusResults, error) { 282 var results params.StatusResults 283 canAccessFunc, err := p.getAuthFunc() 284 if err != nil { 285 return results, err 286 } 287 // TODO (wallyworld) - add state.State API for more efficient machines query 288 machines, err := p.st.AllMachines() 289 if err != nil { 290 return results, err 291 } 292 for _, machine := range machines { 293 if !canAccessFunc(machine.Tag()) { 294 continue 295 } 296 if _, provisionedErr := machine.InstanceId(); provisionedErr == nil { 297 // Machine may have been provisioned but machiner hasn't set the 298 // status to Started yet. 299 continue 300 } 301 var result params.StatusResult 302 var st state.Status 303 st, result.Info, result.Data, err = machine.Status() 304 if err != nil { 305 continue 306 } 307 result.Status = params.Status(st) 308 if result.Status != params.StatusError { 309 continue 310 } 311 // Transient errors are marked as such in the status data. 312 if transient, ok := result.Data["transient"].(bool); !ok || !transient { 313 continue 314 } 315 result.Id = machine.Id() 316 result.Life = params.Life(machine.Life().String()) 317 results.Results = append(results.Results, result) 318 } 319 return results, nil 320 } 321 322 // Series returns the deployed series for each given machine entity. 323 func (p *ProvisionerAPI) Series(args params.Entities) (params.StringResults, error) { 324 result := params.StringResults{ 325 Results: make([]params.StringResult, len(args.Entities)), 326 } 327 canAccess, err := p.getAuthFunc() 328 if err != nil { 329 return result, err 330 } 331 for i, entity := range args.Entities { 332 tag, err := names.ParseMachineTag(entity.Tag) 333 if err != nil { 334 result.Results[i].Error = common.ServerError(common.ErrPerm) 335 continue 336 } 337 machine, err := p.getMachine(canAccess, tag) 338 if err == nil { 339 result.Results[i].Result = machine.Series() 340 } 341 result.Results[i].Error = common.ServerError(err) 342 } 343 return result, nil 344 } 345 346 // ProvisioningInfo returns the provisioning information for each given machine entity. 347 func (p *ProvisionerAPI) ProvisioningInfo(args params.Entities) (params.ProvisioningInfoResults, error) { 348 result := params.ProvisioningInfoResults{ 349 Results: make([]params.ProvisioningInfoResult, len(args.Entities)), 350 } 351 canAccess, err := p.getAuthFunc() 352 if err != nil { 353 return result, err 354 } 355 for i, entity := range args.Entities { 356 tag, err := names.ParseMachineTag(entity.Tag) 357 if err != nil { 358 result.Results[i].Error = common.ServerError(common.ErrPerm) 359 continue 360 } 361 machine, err := p.getMachine(canAccess, tag) 362 if err == nil { 363 result.Results[i].Result, err = getProvisioningInfo(machine) 364 } 365 result.Results[i].Error = common.ServerError(err) 366 } 367 return result, nil 368 } 369 370 func getProvisioningInfo(m *state.Machine) (*params.ProvisioningInfo, error) { 371 cons, err := m.Constraints() 372 if err != nil { 373 return nil, err 374 } 375 volumes, err := machineVolumeParams(m) 376 if err != nil { 377 return nil, errors.Trace(err) 378 } 379 // TODO(dimitern) For now, since network names and 380 // provider ids are the same, we return what we got 381 // from state. In the future, when networks can be 382 // added before provisioning, we should convert both 383 // slices from juju network names to provider-specific 384 // ids before returning them. 385 networks, err := m.RequestedNetworks() 386 if err != nil { 387 return nil, err 388 } 389 var jobs []multiwatcher.MachineJob 390 for _, job := range m.Jobs() { 391 jobs = append(jobs, job.ToParams()) 392 } 393 return ¶ms.ProvisioningInfo{ 394 Constraints: cons, 395 Series: m.Series(), 396 Placement: m.Placement(), 397 Networks: networks, 398 Jobs: jobs, 399 Volumes: volumes, 400 }, nil 401 } 402 403 // DistributionGroup returns, for each given machine entity, 404 // a slice of instance.Ids that belong to the same distribution 405 // group as that machine. This information may be used to 406 // distribute instances for high availability. 407 func (p *ProvisionerAPI) DistributionGroup(args params.Entities) (params.DistributionGroupResults, error) { 408 result := params.DistributionGroupResults{ 409 Results: make([]params.DistributionGroupResult, len(args.Entities)), 410 } 411 canAccess, err := p.getAuthFunc() 412 if err != nil { 413 return result, err 414 } 415 for i, entity := range args.Entities { 416 tag, err := names.ParseMachineTag(entity.Tag) 417 if err != nil { 418 result.Results[i].Error = common.ServerError(common.ErrPerm) 419 continue 420 } 421 machine, err := p.getMachine(canAccess, tag) 422 if err == nil { 423 // If the machine is an environment manager, return 424 // environment manager instances. Otherwise, return 425 // instances with services in common with the machine 426 // being provisioned. 427 if machine.IsManager() { 428 result.Results[i].Result, err = environManagerInstances(p.st) 429 } else { 430 result.Results[i].Result, err = commonServiceInstances(p.st, machine) 431 } 432 } 433 result.Results[i].Error = common.ServerError(err) 434 } 435 return result, nil 436 } 437 438 // environManagerInstances returns all environ manager instances. 439 func environManagerInstances(st *state.State) ([]instance.Id, error) { 440 info, err := st.StateServerInfo() 441 if err != nil { 442 return nil, err 443 } 444 instances := make([]instance.Id, 0, len(info.MachineIds)) 445 for _, id := range info.MachineIds { 446 machine, err := st.Machine(id) 447 if err != nil { 448 return nil, err 449 } 450 instanceId, err := machine.InstanceId() 451 if err == nil { 452 instances = append(instances, instanceId) 453 } else if !errors.IsNotProvisioned(err) { 454 return nil, err 455 } 456 } 457 return instances, nil 458 } 459 460 // commonServiceInstances returns instances with 461 // services in common with the specified machine. 462 func commonServiceInstances(st *state.State, m *state.Machine) ([]instance.Id, error) { 463 units, err := m.Units() 464 if err != nil { 465 return nil, err 466 } 467 instanceIdSet := make(set.Strings) 468 for _, unit := range units { 469 if !unit.IsPrincipal() { 470 continue 471 } 472 instanceIds, err := state.ServiceInstances(st, unit.ServiceName()) 473 if err != nil { 474 return nil, err 475 } 476 for _, instanceId := range instanceIds { 477 instanceIdSet.Add(string(instanceId)) 478 } 479 } 480 instanceIds := make([]instance.Id, instanceIdSet.Size()) 481 // Sort values to simplify testing. 482 for i, instanceId := range instanceIdSet.SortedValues() { 483 instanceIds[i] = instance.Id(instanceId) 484 } 485 return instanceIds, nil 486 } 487 488 // Constraints returns the constraints for each given machine entity. 489 func (p *ProvisionerAPI) Constraints(args params.Entities) (params.ConstraintsResults, error) { 490 result := params.ConstraintsResults{ 491 Results: make([]params.ConstraintsResult, len(args.Entities)), 492 } 493 canAccess, err := p.getAuthFunc() 494 if err != nil { 495 return result, err 496 } 497 for i, entity := range args.Entities { 498 tag, err := names.ParseMachineTag(entity.Tag) 499 if err != nil { 500 result.Results[i].Error = common.ServerError(common.ErrPerm) 501 continue 502 } 503 machine, err := p.getMachine(canAccess, tag) 504 if err == nil { 505 var cons constraints.Value 506 cons, err = machine.Constraints() 507 if err == nil { 508 result.Results[i].Constraints = cons 509 } 510 } 511 result.Results[i].Error = common.ServerError(err) 512 } 513 return result, nil 514 } 515 516 // machineVolumeParams retrieves VolumeParams for the volumes that should be 517 // provisioned with and attached to the machine. The client should ignore 518 // parameters that it does not know how to handle. 519 func machineVolumeParams(m *state.Machine) ([]storage.VolumeParams, error) { 520 blockDevices, err := m.BlockDevices() 521 if err != nil { 522 return nil, err 523 } 524 if len(blockDevices) == 0 { 525 return nil, nil 526 } 527 allParams := make([]storage.VolumeParams, len(blockDevices)) 528 for i, dev := range blockDevices { 529 params, ok := dev.Params() 530 if !ok { 531 return nil, errors.Errorf("cannot get parameters for volume %q", dev.Name()) 532 } 533 allParams[i] = storage.VolumeParams{ 534 dev.Name(), 535 params.Size, 536 // TODO(axw) when pools are implemented, 537 // set Options here. 538 nil, 539 "", // no instance ID yet 540 } 541 } 542 return allParams, nil 543 } 544 545 // blockDevicesToState converts a slice of storage.BlockDevice to a mapping 546 // of block device names to state.BlockDeviceInfo. 547 func blockDevicesToState(in []storage.BlockDevice) (map[string]state.BlockDeviceInfo, error) { 548 m := make(map[string]state.BlockDeviceInfo) 549 for _, dev := range in { 550 if dev.Name == "" { 551 return nil, errors.New("Name is empty") 552 } 553 m[dev.Name] = state.BlockDeviceInfo{ 554 dev.DeviceName, 555 dev.Label, 556 dev.UUID, 557 dev.Serial, 558 dev.Size, 559 dev.FilesystemType, 560 dev.InUse, 561 } 562 } 563 return m, nil 564 } 565 566 func networkParamsToStateParams(networks []params.Network, ifaces []params.NetworkInterface) ( 567 []state.NetworkInfo, []state.NetworkInterfaceInfo, error, 568 ) { 569 stateNetworks := make([]state.NetworkInfo, len(networks)) 570 for i, network := range networks { 571 tag, err := names.ParseNetworkTag(network.Tag) 572 if err != nil { 573 return nil, nil, err 574 } 575 stateNetworks[i] = state.NetworkInfo{ 576 Name: tag.Id(), 577 ProviderId: network.ProviderId, 578 CIDR: network.CIDR, 579 VLANTag: network.VLANTag, 580 } 581 } 582 stateInterfaces := make([]state.NetworkInterfaceInfo, len(ifaces)) 583 for i, iface := range ifaces { 584 tag, err := names.ParseNetworkTag(iface.NetworkTag) 585 if err != nil { 586 return nil, nil, err 587 } 588 stateInterfaces[i] = state.NetworkInterfaceInfo{ 589 MACAddress: iface.MACAddress, 590 NetworkName: tag.Id(), 591 InterfaceName: iface.InterfaceName, 592 IsVirtual: iface.IsVirtual, 593 Disabled: iface.Disabled, 594 } 595 } 596 return stateNetworks, stateInterfaces, nil 597 } 598 599 // RequestedNetworks returns the requested networks for each given 600 // machine entity. Each entry in both lists is returned with its 601 // provider specific id. 602 func (p *ProvisionerAPI) RequestedNetworks(args params.Entities) (params.RequestedNetworksResults, error) { 603 result := params.RequestedNetworksResults{ 604 Results: make([]params.RequestedNetworkResult, len(args.Entities)), 605 } 606 canAccess, err := p.getAuthFunc() 607 if err != nil { 608 return result, err 609 } 610 for i, entity := range args.Entities { 611 tag, err := names.ParseMachineTag(entity.Tag) 612 if err != nil { 613 result.Results[i].Error = common.ServerError(common.ErrPerm) 614 continue 615 } 616 machine, err := p.getMachine(canAccess, tag) 617 if err == nil { 618 var networks []string 619 networks, err = machine.RequestedNetworks() 620 if err == nil { 621 // TODO(dimitern) For now, since network names and 622 // provider ids are the same, we return what we got 623 // from state. In the future, when networks can be 624 // added before provisioning, we should convert both 625 // slices from juju network names to provider-specific 626 // ids before returning them. 627 result.Results[i].Networks = networks 628 } 629 } 630 result.Results[i].Error = common.ServerError(err) 631 } 632 return result, nil 633 } 634 635 // SetProvisioned sets the provider specific instance id, nonce and 636 // metadata for each given machine. Once set, the instance id cannot 637 // be changed. 638 // 639 // TODO(dimitern) This is not used anymore (as of 1.19.0) and is 640 // retained only for backwards-compatibility. It should be removed as 641 // deprecated. SetInstanceInfo is used instead. 642 func (p *ProvisionerAPI) SetProvisioned(args params.SetProvisioned) (params.ErrorResults, error) { 643 result := params.ErrorResults{ 644 Results: make([]params.ErrorResult, len(args.Machines)), 645 } 646 canAccess, err := p.getAuthFunc() 647 if err != nil { 648 return result, err 649 } 650 for i, arg := range args.Machines { 651 tag, err := names.ParseMachineTag(arg.Tag) 652 if err != nil { 653 result.Results[i].Error = common.ServerError(common.ErrPerm) 654 continue 655 } 656 machine, err := p.getMachine(canAccess, tag) 657 if err == nil { 658 err = machine.SetProvisioned(arg.InstanceId, arg.Nonce, arg.Characteristics) 659 } 660 result.Results[i].Error = common.ServerError(err) 661 } 662 return result, nil 663 } 664 665 // SetInstanceInfo sets the provider specific machine id, nonce, 666 // metadata and network info for each given machine. Once set, the 667 // instance id cannot be changed. 668 func (p *ProvisionerAPI) SetInstanceInfo(args params.InstancesInfo) (params.ErrorResults, error) { 669 result := params.ErrorResults{ 670 Results: make([]params.ErrorResult, len(args.Machines)), 671 } 672 canAccess, err := p.getAuthFunc() 673 if err != nil { 674 return result, err 675 } 676 setInstanceInfo := func(arg params.InstanceInfo) error { 677 tag, err := names.ParseMachineTag(arg.Tag) 678 if err != nil { 679 return common.ErrPerm 680 } 681 machine, err := p.getMachine(canAccess, tag) 682 if err != nil { 683 return err 684 } 685 networks, interfaces, err := networkParamsToStateParams(arg.Networks, arg.Interfaces) 686 if err != nil { 687 return err 688 } 689 blockDevices, err := blockDevicesToState(arg.Volumes) 690 if err != nil { 691 return err 692 } 693 if err = machine.SetInstanceInfo( 694 arg.InstanceId, arg.Nonce, arg.Characteristics, 695 networks, interfaces, blockDevices); err != nil { 696 return errors.Annotatef( 697 err, 698 "cannot record provisioning info for %q", 699 arg.InstanceId, 700 ) 701 } 702 return nil 703 } 704 for i, arg := range args.Machines { 705 err := setInstanceInfo(arg) 706 result.Results[i].Error = common.ServerError(err) 707 } 708 return result, nil 709 } 710 711 // WatchMachineErrorRetry returns a NotifyWatcher that notifies when 712 // the provisioner should retry provisioning machines with transient errors. 713 func (p *ProvisionerAPI) WatchMachineErrorRetry() (params.NotifyWatchResult, error) { 714 result := params.NotifyWatchResult{} 715 if !p.authorizer.AuthEnvironManager() { 716 return result, common.ErrPerm 717 } 718 watch := newWatchMachineErrorRetry() 719 // Consume any initial event and forward it to the result. 720 if _, ok := <-watch.Changes(); ok { 721 result.NotifyWatcherId = p.resources.Register(watch) 722 } else { 723 return result, watcher.EnsureErr(watch) 724 } 725 return result, nil 726 }