github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "math/rand" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/names" 13 "github.com/juju/utils/set" 14 15 "github.com/juju/juju/apiserver/common" 16 "github.com/juju/juju/apiserver/common/networkingcommon" 17 "github.com/juju/juju/apiserver/common/storagecommon" 18 "github.com/juju/juju/apiserver/params" 19 "github.com/juju/juju/constraints" 20 "github.com/juju/juju/container" 21 "github.com/juju/juju/environs" 22 "github.com/juju/juju/instance" 23 "github.com/juju/juju/network" 24 "github.com/juju/juju/provider" 25 "github.com/juju/juju/state" 26 "github.com/juju/juju/state/watcher" 27 "github.com/juju/juju/status" 28 ) 29 30 var logger = loggo.GetLogger("juju.apiserver.provisioner") 31 32 func init() { 33 common.RegisterStandardFacade("Provisioner", 1, NewProvisionerAPI) 34 35 // Version 1 has the same set of methods as 0, with the same 36 // signatures, but its ProvisioningInfo returns additional 37 // information. Clients may require version 1 so that they 38 // receive this additional information; otherwise they are 39 // compatible. 40 common.RegisterStandardFacade("Provisioner", 2, NewProvisionerAPI) 41 } 42 43 // ProvisionerAPI provides access to the Provisioner API facade. 44 type ProvisionerAPI struct { 45 *common.Remover 46 *common.StatusSetter 47 *common.StatusGetter 48 *common.DeadEnsurer 49 *common.PasswordChanger 50 *common.LifeGetter 51 *common.StateAddresser 52 *common.APIAddresser 53 *common.ModelWatcher 54 *common.ModelMachinesWatcher 55 *common.InstanceIdGetter 56 *common.ToolsFinder 57 *common.ToolsGetter 58 59 st *state.State 60 resources *common.Resources 61 authorizer common.Authorizer 62 getAuthFunc common.GetAuthFunc 63 } 64 65 // NewProvisionerAPI creates a new server-side ProvisionerAPI facade. 66 func NewProvisionerAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer) (*ProvisionerAPI, error) { 67 if !authorizer.AuthMachineAgent() && !authorizer.AuthModelManager() { 68 return nil, common.ErrPerm 69 } 70 getAuthFunc := func() (common.AuthFunc, error) { 71 isModelManager := authorizer.AuthModelManager() 72 isMachineAgent := authorizer.AuthMachineAgent() 73 authEntityTag := authorizer.GetAuthTag() 74 75 return func(tag names.Tag) bool { 76 if isMachineAgent && tag == authEntityTag { 77 // A machine agent can always access its own machine. 78 return true 79 } 80 switch tag := tag.(type) { 81 case names.MachineTag: 82 parentId := state.ParentId(tag.Id()) 83 if parentId == "" { 84 // All top-level machines are accessible by the 85 // environment manager. 86 return isModelManager 87 } 88 // All containers with the authenticated machine as a 89 // parent are accessible by it. 90 // TODO(dfc) sometimes authEntity tag is nil, which is fine because nil is 91 // only equal to nil, but it suggests someone is passing an authorizer 92 // with a nil tag. 93 return isMachineAgent && names.NewMachineTag(parentId) == authEntityTag 94 default: 95 return false 96 } 97 }, nil 98 } 99 getAuthOwner := func() (common.AuthFunc, error) { 100 return authorizer.AuthOwner, nil 101 } 102 env, err := st.Model() 103 if err != nil { 104 return nil, err 105 } 106 urlGetter := common.NewToolsURLGetter(env.UUID(), st) 107 return &ProvisionerAPI{ 108 Remover: common.NewRemover(st, false, getAuthFunc), 109 StatusSetter: common.NewStatusSetter(st, getAuthFunc), 110 StatusGetter: common.NewStatusGetter(st, getAuthFunc), 111 DeadEnsurer: common.NewDeadEnsurer(st, getAuthFunc), 112 PasswordChanger: common.NewPasswordChanger(st, getAuthFunc), 113 LifeGetter: common.NewLifeGetter(st, getAuthFunc), 114 StateAddresser: common.NewStateAddresser(st), 115 APIAddresser: common.NewAPIAddresser(st, resources), 116 ModelWatcher: common.NewModelWatcher(st, resources, authorizer), 117 ModelMachinesWatcher: common.NewModelMachinesWatcher(st, resources, authorizer), 118 InstanceIdGetter: common.NewInstanceIdGetter(st, getAuthFunc), 119 ToolsFinder: common.NewToolsFinder(st, st, urlGetter), 120 ToolsGetter: common.NewToolsGetter(st, st, st, urlGetter, getAuthOwner), 121 st: st, 122 resources: resources, 123 authorizer: authorizer, 124 getAuthFunc: getAuthFunc, 125 }, nil 126 } 127 128 func (p *ProvisionerAPI) getMachine(canAccess common.AuthFunc, tag names.MachineTag) (*state.Machine, error) { 129 if !canAccess(tag) { 130 return nil, common.ErrPerm 131 } 132 entity, err := p.st.FindEntity(tag) 133 if err != nil { 134 return nil, err 135 } 136 // The authorization function guarantees that the tag represents a 137 // machine. 138 return entity.(*state.Machine), nil 139 } 140 141 func (p *ProvisionerAPI) watchOneMachineContainers(arg params.WatchContainer) (params.StringsWatchResult, error) { 142 nothing := params.StringsWatchResult{} 143 canAccess, err := p.getAuthFunc() 144 if err != nil { 145 return nothing, common.ErrPerm 146 } 147 tag, err := names.ParseMachineTag(arg.MachineTag) 148 if err != nil { 149 return nothing, common.ErrPerm 150 } 151 if !canAccess(tag) { 152 return nothing, common.ErrPerm 153 } 154 machine, err := p.st.Machine(tag.Id()) 155 if err != nil { 156 return nothing, err 157 } 158 var watch state.StringsWatcher 159 if arg.ContainerType != "" { 160 watch = machine.WatchContainers(instance.ContainerType(arg.ContainerType)) 161 } else { 162 watch = machine.WatchAllContainers() 163 } 164 // Consume the initial event and forward it to the result. 165 if changes, ok := <-watch.Changes(); ok { 166 return params.StringsWatchResult{ 167 StringsWatcherId: p.resources.Register(watch), 168 Changes: changes, 169 }, nil 170 } 171 return nothing, watcher.EnsureErr(watch) 172 } 173 174 // WatchContainers starts a StringsWatcher to watch containers deployed to 175 // any machine passed in args. 176 func (p *ProvisionerAPI) WatchContainers(args params.WatchContainers) (params.StringsWatchResults, error) { 177 result := params.StringsWatchResults{ 178 Results: make([]params.StringsWatchResult, len(args.Params)), 179 } 180 for i, arg := range args.Params { 181 watcherResult, err := p.watchOneMachineContainers(arg) 182 result.Results[i] = watcherResult 183 result.Results[i].Error = common.ServerError(err) 184 } 185 return result, nil 186 } 187 188 // WatchAllContainers starts a StringsWatcher to watch all containers deployed to 189 // any machine passed in args. 190 func (p *ProvisionerAPI) WatchAllContainers(args params.WatchContainers) (params.StringsWatchResults, error) { 191 return p.WatchContainers(args) 192 } 193 194 // SetSupportedContainers updates the list of containers supported by the machines passed in args. 195 func (p *ProvisionerAPI) SetSupportedContainers(args params.MachineContainersParams) (params.ErrorResults, error) { 196 result := params.ErrorResults{ 197 Results: make([]params.ErrorResult, len(args.Params)), 198 } 199 200 canAccess, err := p.getAuthFunc() 201 if err != nil { 202 return result, err 203 } 204 for i, arg := range args.Params { 205 tag, err := names.ParseMachineTag(arg.MachineTag) 206 if err != nil { 207 result.Results[i].Error = common.ServerError(common.ErrPerm) 208 continue 209 } 210 machine, err := p.getMachine(canAccess, tag) 211 if err != nil { 212 result.Results[i].Error = common.ServerError(err) 213 continue 214 } 215 if len(arg.ContainerTypes) == 0 { 216 err = machine.SupportsNoContainers() 217 } else { 218 err = machine.SetSupportedContainers(arg.ContainerTypes) 219 } 220 if err != nil { 221 result.Results[i].Error = common.ServerError(err) 222 } 223 } 224 return result, nil 225 } 226 227 // ContainerManagerConfig returns information from the environment config that is 228 // needed for configuring the container manager. 229 func (p *ProvisionerAPI) ContainerManagerConfig(args params.ContainerManagerConfigParams) (params.ContainerManagerConfig, error) { 230 var result params.ContainerManagerConfig 231 config, err := p.st.ModelConfig() 232 if err != nil { 233 return result, err 234 } 235 cfg := make(map[string]string) 236 cfg[container.ConfigName] = container.DefaultNamespace 237 238 switch args.Type { 239 case instance.LXC: 240 if useLxcClone, ok := config.LXCUseClone(); ok { 241 cfg["use-clone"] = fmt.Sprint(useLxcClone) 242 } 243 if useLxcCloneAufs, ok := config.LXCUseCloneAUFS(); ok { 244 cfg["use-aufs"] = fmt.Sprint(useLxcCloneAufs) 245 } 246 if lxcDefaultMTU, ok := config.LXCDefaultMTU(); ok { 247 logger.Debugf("using default MTU %v for all LXC containers NICs", lxcDefaultMTU) 248 cfg[container.ConfigLXCDefaultMTU] = fmt.Sprintf("%d", lxcDefaultMTU) 249 } 250 case instance.LXD: 251 // TODO(jam): DefaultMTU needs to be handled here 252 // TODO(jam): Do we want to handle ImageStream here, or do we 253 // hide it from them? (all cached images must come from the 254 // same image stream?) 255 } 256 257 if !environs.AddressAllocationEnabled(config.Type()) { 258 // No need to even try checking the environ for support. 259 logger.Debugf("address allocation feature flag not enabled") 260 result.ManagerConfig = cfg 261 return result, nil 262 } 263 264 // Create an environment to verify networking support. 265 env, err := environs.New(config) 266 if err != nil { 267 return result, err 268 } 269 if netEnv, ok := environs.SupportsNetworking(env); ok { 270 // Passing network.AnySubnet below should be interpreted by 271 // the provider as "does ANY subnet support this". 272 supported, err := netEnv.SupportsAddressAllocation(network.AnySubnet) 273 if err == nil && supported { 274 cfg[container.ConfigIPForwarding] = "true" 275 } else if err != nil { 276 // We log the error, but it's safe to ignore as it's not 277 // critical. 278 logger.Debugf("address allocation not supported (%v)", err) 279 } 280 // AWS requires NAT in place in order for hosted containers to 281 // reach outside. 282 if config.Type() == provider.EC2 { 283 cfg[container.ConfigEnableNAT] = "true" 284 } 285 } 286 287 result.ManagerConfig = cfg 288 return result, nil 289 } 290 291 // ContainerConfig returns information from the environment config that is 292 // needed for container cloud-init. 293 func (p *ProvisionerAPI) ContainerConfig() (params.ContainerConfig, error) { 294 result := params.ContainerConfig{} 295 config, err := p.st.ModelConfig() 296 if err != nil { 297 return result, err 298 } 299 300 result.UpdateBehavior = ¶ms.UpdateBehavior{ 301 config.EnableOSRefreshUpdate(), 302 config.EnableOSUpgrade(), 303 } 304 result.ProviderType = config.Type() 305 result.AuthorizedKeys = config.AuthorizedKeys() 306 result.SSLHostnameVerification = config.SSLHostnameVerification() 307 result.Proxy = config.ProxySettings() 308 result.AptProxy = config.AptProxySettings() 309 result.AptMirror = config.AptMirror() 310 result.PreferIPv6 = config.PreferIPv6() 311 result.AllowLXCLoopMounts, _ = config.AllowLXCLoopMounts() 312 313 return result, nil 314 } 315 316 // MachinesWithTransientErrors returns status data for machines with provisioning 317 // errors which are transient. 318 func (p *ProvisionerAPI) MachinesWithTransientErrors() (params.StatusResults, error) { 319 var results params.StatusResults 320 canAccessFunc, err := p.getAuthFunc() 321 if err != nil { 322 return results, err 323 } 324 // TODO (wallyworld) - add state.State API for more efficient machines query 325 machines, err := p.st.AllMachines() 326 if err != nil { 327 return results, err 328 } 329 for _, machine := range machines { 330 if !canAccessFunc(machine.Tag()) { 331 continue 332 } 333 if _, provisionedErr := machine.InstanceId(); provisionedErr == nil { 334 // Machine may have been provisioned but machiner hasn't set the 335 // status to Started yet. 336 continue 337 } 338 var result params.StatusResult 339 statusInfo, err := machine.Status() 340 if err != nil { 341 continue 342 } 343 result.Status = status.Status(statusInfo.Status) 344 result.Info = statusInfo.Message 345 result.Data = statusInfo.Data 346 if result.Status != status.StatusError { 347 continue 348 } 349 // Transient errors are marked as such in the status data. 350 if transient, ok := result.Data["transient"].(bool); !ok || !transient { 351 continue 352 } 353 result.Id = machine.Id() 354 result.Life = params.Life(machine.Life().String()) 355 results.Results = append(results.Results, result) 356 } 357 return results, nil 358 } 359 360 // Series returns the deployed series for each given machine entity. 361 func (p *ProvisionerAPI) Series(args params.Entities) (params.StringResults, error) { 362 result := params.StringResults{ 363 Results: make([]params.StringResult, len(args.Entities)), 364 } 365 canAccess, err := p.getAuthFunc() 366 if err != nil { 367 return result, err 368 } 369 for i, entity := range args.Entities { 370 tag, err := names.ParseMachineTag(entity.Tag) 371 if err != nil { 372 result.Results[i].Error = common.ServerError(common.ErrPerm) 373 continue 374 } 375 machine, err := p.getMachine(canAccess, tag) 376 if err == nil { 377 result.Results[i].Result = machine.Series() 378 } 379 result.Results[i].Error = common.ServerError(err) 380 } 381 return result, nil 382 } 383 384 // DistributionGroup returns, for each given machine entity, 385 // a slice of instance.Ids that belong to the same distribution 386 // group as that machine. This information may be used to 387 // distribute instances for high availability. 388 func (p *ProvisionerAPI) DistributionGroup(args params.Entities) (params.DistributionGroupResults, error) { 389 result := params.DistributionGroupResults{ 390 Results: make([]params.DistributionGroupResult, len(args.Entities)), 391 } 392 canAccess, err := p.getAuthFunc() 393 if err != nil { 394 return result, err 395 } 396 for i, entity := range args.Entities { 397 tag, err := names.ParseMachineTag(entity.Tag) 398 if err != nil { 399 result.Results[i].Error = common.ServerError(common.ErrPerm) 400 continue 401 } 402 machine, err := p.getMachine(canAccess, tag) 403 if err == nil { 404 // If the machine is an environment manager, return 405 // environment manager instances. Otherwise, return 406 // instances with services in common with the machine 407 // being provisioned. 408 if machine.IsManager() { 409 result.Results[i].Result, err = environManagerInstances(p.st) 410 } else { 411 result.Results[i].Result, err = commonServiceInstances(p.st, machine) 412 } 413 } 414 result.Results[i].Error = common.ServerError(err) 415 } 416 return result, nil 417 } 418 419 // environManagerInstances returns all environ manager instances. 420 func environManagerInstances(st *state.State) ([]instance.Id, error) { 421 info, err := st.ControllerInfo() 422 if err != nil { 423 return nil, err 424 } 425 instances := make([]instance.Id, 0, len(info.MachineIds)) 426 for _, id := range info.MachineIds { 427 machine, err := st.Machine(id) 428 if err != nil { 429 return nil, err 430 } 431 instanceId, err := machine.InstanceId() 432 if err == nil { 433 instances = append(instances, instanceId) 434 } else if !errors.IsNotProvisioned(err) { 435 return nil, err 436 } 437 } 438 return instances, nil 439 } 440 441 // commonServiceInstances returns instances with 442 // services in common with the specified machine. 443 func commonServiceInstances(st *state.State, m *state.Machine) ([]instance.Id, error) { 444 units, err := m.Units() 445 if err != nil { 446 return nil, err 447 } 448 instanceIdSet := make(set.Strings) 449 for _, unit := range units { 450 if !unit.IsPrincipal() { 451 continue 452 } 453 instanceIds, err := state.ServiceInstances(st, unit.ServiceName()) 454 if err != nil { 455 return nil, err 456 } 457 for _, instanceId := range instanceIds { 458 instanceIdSet.Add(string(instanceId)) 459 } 460 } 461 instanceIds := make([]instance.Id, instanceIdSet.Size()) 462 // Sort values to simplify testing. 463 for i, instanceId := range instanceIdSet.SortedValues() { 464 instanceIds[i] = instance.Id(instanceId) 465 } 466 return instanceIds, nil 467 } 468 469 // Constraints returns the constraints for each given machine entity. 470 func (p *ProvisionerAPI) Constraints(args params.Entities) (params.ConstraintsResults, error) { 471 result := params.ConstraintsResults{ 472 Results: make([]params.ConstraintsResult, len(args.Entities)), 473 } 474 canAccess, err := p.getAuthFunc() 475 if err != nil { 476 return result, err 477 } 478 for i, entity := range args.Entities { 479 tag, err := names.ParseMachineTag(entity.Tag) 480 if err != nil { 481 result.Results[i].Error = common.ServerError(common.ErrPerm) 482 continue 483 } 484 machine, err := p.getMachine(canAccess, tag) 485 if err == nil { 486 var cons constraints.Value 487 cons, err = machine.Constraints() 488 if err == nil { 489 result.Results[i].Constraints = cons 490 } 491 } 492 result.Results[i].Error = common.ServerError(err) 493 } 494 return result, nil 495 } 496 497 // SetInstanceInfo sets the provider specific machine id, nonce, 498 // metadata and network info for each given machine. Once set, the 499 // instance id cannot be changed. 500 func (p *ProvisionerAPI) SetInstanceInfo(args params.InstancesInfo) (params.ErrorResults, error) { 501 result := params.ErrorResults{ 502 Results: make([]params.ErrorResult, len(args.Machines)), 503 } 504 canAccess, err := p.getAuthFunc() 505 if err != nil { 506 return result, err 507 } 508 setInstanceInfo := func(arg params.InstanceInfo) error { 509 tag, err := names.ParseMachineTag(arg.Tag) 510 if err != nil { 511 return common.ErrPerm 512 } 513 machine, err := p.getMachine(canAccess, tag) 514 if err != nil { 515 return err 516 } 517 volumes, err := storagecommon.VolumesToState(arg.Volumes) 518 if err != nil { 519 return err 520 } 521 volumeAttachments, err := storagecommon.VolumeAttachmentInfosToState(arg.VolumeAttachments) 522 if err != nil { 523 return err 524 } 525 526 devicesArgs, devicesAddrs := networkingcommon.NetworkConfigsToStateArgs(arg.NetworkConfig) 527 528 err = machine.SetInstanceInfo( 529 arg.InstanceId, arg.Nonce, arg.Characteristics, 530 devicesArgs, devicesAddrs, 531 volumes, volumeAttachments, 532 ) 533 if err != nil { 534 return errors.Annotatef(err, "cannot record provisioning info for %q", arg.InstanceId) 535 } 536 return nil 537 } 538 for i, arg := range args.Machines { 539 err := setInstanceInfo(arg) 540 result.Results[i].Error = common.ServerError(err) 541 } 542 return result, nil 543 } 544 545 // WatchMachineErrorRetry returns a NotifyWatcher that notifies when 546 // the provisioner should retry provisioning machines with transient errors. 547 func (p *ProvisionerAPI) WatchMachineErrorRetry() (params.NotifyWatchResult, error) { 548 result := params.NotifyWatchResult{} 549 if !p.authorizer.AuthModelManager() { 550 return result, common.ErrPerm 551 } 552 watch := newWatchMachineErrorRetry() 553 // Consume any initial event and forward it to the result. 554 if _, ok := <-watch.Changes(); ok { 555 result.NotifyWatcherId = p.resources.Register(watch) 556 } else { 557 return result, watcher.EnsureErr(watch) 558 } 559 return result, nil 560 } 561 562 func containerHostname(containerTag names.Tag) string { 563 return fmt.Sprintf("%s-%s", container.DefaultNamespace, containerTag.String()) 564 } 565 566 // ReleaseContainerAddresses finds addresses allocated to a container 567 // and marks them as Dead, to be released and removed. It accepts 568 // container tags as arguments. If address allocation feature flag is 569 // not enabled, it will return a NotSupported error. 570 func (p *ProvisionerAPI) ReleaseContainerAddresses(args params.Entities) (params.ErrorResults, error) { 571 result := params.ErrorResults{ 572 Results: make([]params.ErrorResult, len(args.Entities)), 573 } 574 575 canAccess, err := p.getAuthFunc() 576 if err != nil { 577 logger.Errorf("failed to get an authorisation function: %v", err) 578 return result, errors.Trace(err) 579 } 580 // Loop over the passed container tags. 581 for i, entity := range args.Entities { 582 tag, err := names.ParseMachineTag(entity.Tag) 583 if err != nil { 584 logger.Warningf("failed to parse machine tag %q: %v", entity.Tag, err) 585 result.Results[i].Error = common.ServerError(common.ErrPerm) 586 continue 587 } 588 589 // The auth function (canAccess) checks that the machine is a 590 // top level machine (we filter those out next) or that the 591 // machine has the host as a parent. 592 container, err := p.getMachine(canAccess, tag) 593 if err != nil { 594 logger.Warningf("failed to get machine %q: %v", tag, err) 595 result.Results[i].Error = common.ServerError(err) 596 continue 597 } else if !container.IsContainer() { 598 err = errors.Errorf("cannot mark addresses for removal for %q: not a container", tag) 599 result.Results[i].Error = common.ServerError(err) 600 continue 601 } 602 603 id := container.Id() 604 addresses, err := p.st.AllocatedIPAddresses(id) 605 if err != nil { 606 logger.Warningf("failed to get Id for container %q: %v", tag, err) 607 result.Results[i].Error = common.ServerError(err) 608 continue 609 } 610 611 deadErrors := []error{} 612 logger.Debugf("for container %q found addresses %v", tag, addresses) 613 for _, addr := range addresses { 614 err = addr.EnsureDead() 615 if err != nil { 616 deadErrors = append(deadErrors, err) 617 continue 618 } 619 } 620 if len(deadErrors) != 0 { 621 err = errors.Errorf("failed to mark all addresses for removal for %q: %v", tag, deadErrors) 622 result.Results[i].Error = common.ServerError(err) 623 } 624 } 625 626 return result, nil 627 } 628 629 func (p *ProvisionerAPI) legacyAddressAllocationSupported() (bool, error) { 630 config, err := p.st.ModelConfig() 631 if err != nil { 632 return false, errors.Trace(err) 633 } 634 return environs.AddressAllocationEnabled(config.Type()), nil 635 } 636 637 // PrepareContainerInterfaceInfo allocates an address and returns 638 // information to configure networking for a container. It accepts 639 // container tags as arguments. If the address allocation feature flag 640 // is not enabled, it returns a NotSupported error. 641 func (p *ProvisionerAPI) PrepareContainerInterfaceInfo(args params.Entities) ( 642 params.MachineNetworkConfigResults, 643 error, 644 ) { 645 supported, err := p.legacyAddressAllocationSupported() 646 if err != nil { 647 return params.MachineNetworkConfigResults{}, errors.Trace(err) 648 } 649 if supported { 650 logger.Warningf("address allocation enabled - using legacyPrepareOrGetContainerInterfaceInfo(true)") 651 return p.legacyPrepareOrGetContainerInterfaceInfo(args, true) 652 } 653 return p.prepareOrGetContainerInterfaceInfo(args, true) 654 } 655 656 // GetContainerInterfaceInfo returns information to configure networking 657 // for a container. It accepts container tags as arguments. If the address 658 // allocation feature flag is not enabled, it returns a NotSupported error. 659 func (p *ProvisionerAPI) GetContainerInterfaceInfo(args params.Entities) ( 660 params.MachineNetworkConfigResults, 661 error, 662 ) { 663 supported, err := p.legacyAddressAllocationSupported() 664 if err != nil { 665 return params.MachineNetworkConfigResults{}, errors.Trace(err) 666 } 667 if supported { 668 logger.Warningf("address allocation enabled - using legacyPrepareOrGetContainerInterfaceInfo(false)") 669 return p.legacyPrepareOrGetContainerInterfaceInfo(args, false) 670 } 671 return p.prepareOrGetContainerInterfaceInfo(args, false) 672 } 673 674 // MACAddressTemplate is used to generate a unique MAC address for a 675 // container. Every '%x' is replaced by a random hexadecimal digit, 676 // while the rest is kept as-is. 677 const MACAddressTemplate = "00:16:3e:%02x:%02x:%02x" 678 679 // generateMACAddress creates a random MAC address within the space defined by 680 // MACAddressTemplate above. 681 // 682 // TODO(dimitern): We should make a best effort to ensure the MAC address we 683 // generate is unique at least within the current environment. 684 func generateMACAddress() string { 685 digits := make([]interface{}, 3) 686 for i := range digits { 687 digits[i] = rand.Intn(256) 688 } 689 return fmt.Sprintf(MACAddressTemplate, digits...) 690 } 691 692 func (p *ProvisionerAPI) prepareOrGetContainerInterfaceInfo(args params.Entities, maintain bool) (params.MachineNetworkConfigResults, error) { 693 result := params.MachineNetworkConfigResults{ 694 Results: make([]params.MachineNetworkConfigResult, len(args.Entities)), 695 } 696 697 netEnviron, hostMachine, canAccess, err := p.prepareContainerAccessEnvironment() 698 if err != nil { 699 return result, errors.Trace(err) 700 } 701 instId, err := hostMachine.InstanceId() 702 if errors.IsNotProvisioned(err) { 703 err = errors.NotProvisionedf("cannot prepare container network config: host machine %q", hostMachine) 704 return result, err 705 } else if err != nil { 706 return result, errors.Trace(err) 707 } 708 709 for i, entity := range args.Entities { 710 tag, err := names.ParseMachineTag(entity.Tag) 711 if err != nil { 712 result.Results[i].Error = common.ServerError(err) 713 continue 714 } 715 // The auth function (canAccess) checks that the machine is a 716 // top level machine (we filter those out next) or that the 717 // machine has the host as a parent. 718 container, err := p.getMachine(canAccess, tag) 719 if err != nil { 720 result.Results[i].Error = common.ServerError(err) 721 continue 722 } else if !container.IsContainer() { 723 err = errors.Errorf("cannot prepare network config for %q: not a container", tag) 724 result.Results[i].Error = common.ServerError(err) 725 continue 726 } else if ciid, cerr := container.InstanceId(); maintain == true && cerr == nil { 727 // Since we want to configure and create NICs on the 728 // container before it starts, it must also be not 729 // provisioned yet. 730 err = errors.Errorf("container %q already provisioned as %q", container, ciid) 731 result.Results[i].Error = common.ServerError(err) 732 continue 733 } else if cerr != nil && !errors.IsNotProvisioned(cerr) { 734 // Any other error needs to be reported. 735 result.Results[i].Error = common.ServerError(cerr) 736 continue 737 } 738 739 if err := hostMachine.SetContainerLinkLayerDevices(container); err != nil { 740 result.Results[i].Error = common.ServerError(err) 741 continue 742 } 743 744 containerDevices, err := container.AllLinkLayerDevices() 745 if err != nil { 746 result.Results[i].Error = common.ServerError(err) 747 continue 748 } 749 750 preparedInfo := make([]network.InterfaceInfo, len(containerDevices)) 751 preparedOK := true 752 for j, device := range containerDevices { 753 parentDevice, err := device.ParentDevice() 754 if err != nil || parentDevice == nil { 755 err = errors.Errorf( 756 "cannot get parent %q of container device %q: %v", 757 device.ParentName(), device.Name(), err, 758 ) 759 result.Results[i].Error = common.ServerError(err) 760 preparedOK = false 761 break 762 } 763 parentAddrs, err := parentDevice.Addresses() 764 if err != nil { 765 result.Results[i].Error = common.ServerError(err) 766 preparedOK = false 767 break 768 } 769 if len(parentAddrs) == 0 { 770 err = errors.Errorf("host machine device %q has no addresses", parentDevice.Name()) 771 result.Results[i].Error = common.ServerError(err) 772 preparedOK = false 773 break 774 } 775 firstAddress := parentAddrs[0] 776 parentDeviceSubnet, err := firstAddress.Subnet() 777 if err != nil { 778 err = errors.Annotatef(err, 779 "cannot get subnet %q used by address %q of host machine device %q", 780 firstAddress.SubnetCIDR(), firstAddress.Value(), parentDevice.Name(), 781 ) 782 result.Results[i].Error = common.ServerError(err) 783 preparedOK = false 784 break 785 } 786 787 info := network.InterfaceInfo{ 788 InterfaceName: device.Name(), 789 MACAddress: device.MACAddress(), 790 ConfigType: network.ConfigStatic, 791 InterfaceType: network.InterfaceType(device.Type()), 792 NoAutoStart: !device.IsAutoStart(), 793 Disabled: !device.IsUp(), 794 MTU: int(device.MTU()), 795 CIDR: parentDeviceSubnet.CIDR(), 796 ProviderSubnetId: parentDeviceSubnet.ProviderId(), 797 VLANTag: parentDeviceSubnet.VLANTag(), 798 ParentInterfaceName: parentDevice.Name(), 799 } 800 logger.Tracef("prepared info for container interface %q: %+v", info.InterfaceName, info) 801 preparedOK = true 802 preparedInfo[j] = info 803 } 804 805 if !preparedOK { 806 // Error result is already set. 807 continue 808 } 809 810 allocatedInfo, err := netEnviron.AllocateContainerAddresses(instId, preparedInfo) 811 if err != nil { 812 result.Results[i].Error = common.ServerError(err) 813 continue 814 } 815 logger.Debugf("got allocated info from provider: %+v", allocatedInfo) 816 817 allocatedConfig := networkingcommon.NetworkConfigFromInterfaceInfo(allocatedInfo) 818 sortedAllocatedConfig := networkingcommon.SortNetworkConfigsByInterfaceName(allocatedConfig) 819 logger.Tracef("allocated sorted network config: %+v", sortedAllocatedConfig) 820 result.Results[i].Config = sortedAllocatedConfig 821 } 822 return result, nil 823 } 824 825 // legacyPrepareOrGetContainerInterfaceInfo optionally allocates an address and 826 // returns information for configuring networking on a container. It accepts 827 // container tags as arguments. 828 func (p *ProvisionerAPI) legacyPrepareOrGetContainerInterfaceInfo( 829 args params.Entities, 830 provisionContainer bool, 831 ) ( 832 params.MachineNetworkConfigResults, 833 error, 834 ) { 835 result := params.MachineNetworkConfigResults{ 836 Results: make([]params.MachineNetworkConfigResult, len(args.Entities)), 837 } 838 839 // Some preparations first. 840 environ, host, canAccess, err := p.prepareContainerAccessEnvironment() 841 if err != nil { 842 return result, errors.Trace(err) 843 } 844 instId, err := host.InstanceId() 845 if err != nil && errors.IsNotProvisioned(err) { 846 // If the host machine is not provisioned yet, we have nothing 847 // to do. NotProvisionedf will append " not provisioned" to 848 // the message. 849 err = errors.NotProvisionedf("cannot allocate addresses: host machine %q", host) 850 return result, err 851 } 852 var subnet *state.Subnet 853 var subnetInfo network.SubnetInfo 854 var interfaceInfo network.InterfaceInfo 855 if environs.AddressAllocationEnabled(environ.Config().Type()) { 856 // We don't need a subnet unless we need to allocate a static IP. 857 subnet, subnetInfo, interfaceInfo, err = p.prepareAllocationNetwork(environ, instId) 858 if err != nil { 859 return result, errors.Annotate(err, "cannot allocate addresses") 860 } 861 } else { 862 var allInterfaceInfos []network.InterfaceInfo 863 allInterfaceInfos, err = environ.NetworkInterfaces(instId) 864 if err != nil { 865 return result, errors.Annotatef(err, "cannot instance %q interfaces", instId) 866 } else if len(allInterfaceInfos) == 0 { 867 return result, errors.New("no interfaces available") 868 } 869 // Currently we only support a single NIC per container, so we only need 870 // the information from the host instance's first NIC. 871 logger.Tracef("interfaces for instance %q: %v", instId, allInterfaceInfos) 872 interfaceInfo = allInterfaceInfos[0] 873 } 874 875 // Loop over the passed container tags. 876 for i, entity := range args.Entities { 877 tag, err := names.ParseMachineTag(entity.Tag) 878 if err != nil { 879 result.Results[i].Error = common.ServerError(err) 880 continue 881 } 882 883 // The auth function (canAccess) checks that the machine is a 884 // top level machine (we filter those out next) or that the 885 // machine has the host as a parent. 886 container, err := p.getMachine(canAccess, tag) 887 if err != nil { 888 result.Results[i].Error = common.ServerError(err) 889 continue 890 } else if !container.IsContainer() { 891 err = errors.Errorf("cannot allocate address for %q: not a container", tag) 892 result.Results[i].Error = common.ServerError(err) 893 continue 894 } else if ciid, cerr := container.InstanceId(); provisionContainer == true && cerr == nil { 895 // Since we want to configure and create NICs on the 896 // container before it starts, it must also be not 897 // provisioned yet. 898 err = errors.Errorf("container %q already provisioned as %q", container, ciid) 899 result.Results[i].Error = common.ServerError(err) 900 continue 901 } else if cerr != nil && !errors.IsNotProvisioned(cerr) { 902 // Any other error needs to be reported. 903 result.Results[i].Error = common.ServerError(cerr) 904 continue 905 } 906 907 var macAddress string 908 var address *state.IPAddress 909 if provisionContainer { 910 // Allocate and set an address. 911 macAddress = generateMACAddress() 912 address, err = p.allocateAddress(environ, subnet, host, container, instId, macAddress) 913 if err != nil { 914 err = errors.Annotatef(err, "failed to allocate an address for %q", container) 915 result.Results[i].Error = common.ServerError(err) 916 continue 917 } 918 } else { 919 id := container.Id() 920 addresses, err := p.st.AllocatedIPAddresses(id) 921 if err != nil { 922 logger.Warningf("failed to get Id for container %q: %v", tag, err) 923 result.Results[i].Error = common.ServerError(err) 924 continue 925 } 926 // TODO(dooferlad): if we get more than 1 address back, we ignore everything after 927 // the first. The calling function expects exactly one result though, 928 // so we don't appear to have a way of allocating >1 address to a 929 // container... 930 if len(addresses) != 1 { 931 logger.Warningf("got %d addresses for container %q - expected 1: %v", len(addresses), tag, err) 932 result.Results[i].Error = common.ServerError(err) 933 continue 934 } 935 address = addresses[0] 936 macAddress = address.MACAddress() 937 } 938 939 // Store it on the machine, construct and set an interface result. 940 dnsServers := make([]string, len(interfaceInfo.DNSServers)) 941 for l, dns := range interfaceInfo.DNSServers { 942 dnsServers[l] = dns.Value 943 } 944 945 if macAddress == "" { 946 macAddress = interfaceInfo.MACAddress 947 } 948 949 interfaceType := string(interfaceInfo.InterfaceType) 950 if interfaceType == "" { 951 interfaceType = string(network.EthernetInterface) 952 } 953 954 result.Results[i] = params.MachineNetworkConfigResult{ 955 Config: []params.NetworkConfig{{ 956 DeviceIndex: interfaceInfo.DeviceIndex, 957 MACAddress: macAddress, 958 CIDR: subnetInfo.CIDR, 959 ProviderId: string(interfaceInfo.ProviderId), 960 ProviderSubnetId: string(subnetInfo.ProviderId), 961 VLANTag: interfaceInfo.VLANTag, 962 InterfaceType: interfaceType, 963 InterfaceName: interfaceInfo.InterfaceName, 964 Disabled: interfaceInfo.Disabled, 965 NoAutoStart: interfaceInfo.NoAutoStart, 966 DNSServers: dnsServers, 967 ConfigType: string(network.ConfigStatic), 968 Address: address.Value(), 969 GatewayAddress: interfaceInfo.GatewayAddress.Value, 970 }}, 971 } 972 } 973 return result, nil 974 } 975 976 // prepareContainerAccessEnvironment retrieves the environment, host machine, and access 977 // for working with containers. 978 func (p *ProvisionerAPI) prepareContainerAccessEnvironment() (environs.NetworkingEnviron, *state.Machine, common.AuthFunc, error) { 979 netEnviron, err := networkingcommon.NetworkingEnvironFromModelConfig(p.st) 980 if err != nil { 981 return nil, nil, nil, errors.Trace(err) 982 } 983 984 canAccess, err := p.getAuthFunc() 985 if err != nil { 986 return nil, nil, nil, errors.Annotate(err, "cannot authenticate request") 987 } 988 hostAuthTag := p.authorizer.GetAuthTag() 989 if hostAuthTag == nil { 990 return nil, nil, nil, errors.Errorf("authenticated entity tag is nil") 991 } 992 hostTag, err := names.ParseMachineTag(hostAuthTag.String()) 993 if err != nil { 994 return nil, nil, nil, errors.Trace(err) 995 } 996 host, err := p.getMachine(canAccess, hostTag) 997 if err != nil { 998 return nil, nil, nil, errors.Trace(err) 999 } 1000 return netEnviron, host, canAccess, nil 1001 } 1002 1003 // prepareAllocationNetwork retrieves the subnet, its info, and the interface info 1004 // for the allocations. 1005 func (p *ProvisionerAPI) prepareAllocationNetwork( 1006 environ environs.NetworkingEnviron, 1007 instId instance.Id, 1008 ) ( 1009 *state.Subnet, 1010 network.SubnetInfo, 1011 network.InterfaceInfo, 1012 error, 1013 ) { 1014 var subnetInfo network.SubnetInfo 1015 var interfaceInfo network.InterfaceInfo 1016 1017 interfaces, err := environ.NetworkInterfaces(instId) 1018 if err != nil { 1019 return nil, subnetInfo, interfaceInfo, errors.Trace(err) 1020 } else if len(interfaces) == 0 { 1021 return nil, subnetInfo, interfaceInfo, errors.New("no interfaces available") 1022 } 1023 logger.Tracef("interfaces for instance %q: %v", instId, interfaces) 1024 1025 subnetSet := make(set.Strings) 1026 subnetIds := []network.Id{} 1027 subnetIdToInterface := make(map[network.Id]network.InterfaceInfo) 1028 for _, iface := range interfaces { 1029 if iface.ProviderSubnetId == "" { 1030 logger.Debugf("no subnet associated with interface %#v (skipping)", iface) 1031 continue 1032 } else if iface.Disabled { 1033 logger.Debugf("interface %#v disabled (skipping)", iface) 1034 continue 1035 } 1036 if !subnetSet.Contains(string(iface.ProviderSubnetId)) { 1037 subnetIds = append(subnetIds, iface.ProviderSubnetId) 1038 subnetSet.Add(string(iface.ProviderSubnetId)) 1039 1040 // This means that multiple interfaces on the same subnet will 1041 // only appear once. 1042 subnetIdToInterface[iface.ProviderSubnetId] = iface 1043 } 1044 } 1045 subnets, err := environ.Subnets(instId, subnetIds) 1046 if err != nil { 1047 return nil, subnetInfo, interfaceInfo, errors.Trace(err) 1048 } else if len(subnets) == 0 { 1049 return nil, subnetInfo, interfaceInfo, errors.Errorf("no subnets available") 1050 } 1051 logger.Tracef("subnets for instance %q: %v", instId, subnets) 1052 1053 // TODO(mfoord): we need a better strategy for picking a subnet to 1054 // allocate an address on. (dimitern): Right now we just pick the 1055 // first subnet with allocatable range set. Instead, we should 1056 // allocate an address per interface, assuming each interface is 1057 // on a subnet with allocatable range set, and skipping those 1058 // which do not have a range set. 1059 var success bool 1060 for _, sub := range subnets { 1061 logger.Tracef("trying to allocate a static IP on subnet %q", sub.ProviderId) 1062 if sub.AllocatableIPHigh == nil { 1063 logger.Tracef("ignoring subnet %q - no allocatable range set", sub.ProviderId) 1064 // this subnet has no allocatable IPs 1065 continue 1066 } 1067 if sub.AllocatableIPLow != nil && sub.AllocatableIPLow.To4() == nil { 1068 logger.Tracef("ignoring IPv6 subnet %q - allocating IPv6 addresses not yet supported", sub.ProviderId) 1069 // Until we change the way we pick addresses, IPv6 subnets with 1070 // their *huge* ranges (/64 being the default), there is no point in 1071 // allowing such subnets (it won't even work as PickNewAddress() 1072 // assumes IPv4 allocatable range anyway). 1073 continue 1074 } 1075 ok, err := environ.SupportsAddressAllocation(sub.ProviderId) 1076 if err == nil && ok { 1077 subnetInfo = sub 1078 interfaceInfo = subnetIdToInterface[sub.ProviderId] 1079 1080 // Since with addressable containers the host acts like a gateway 1081 // for the containers, instead of using the same gateway for the 1082 // containers as their host's 1083 interfaceInfo.GatewayAddress.Value = interfaceInfo.Address.Value 1084 1085 success = true 1086 break 1087 } 1088 logger.Tracef( 1089 "subnet %q supports address allocation: %v (error: %v)", 1090 sub.ProviderId, ok, err, 1091 ) 1092 } 1093 if !success { 1094 // " not supported" will be appended to the message below. 1095 return nil, subnetInfo, interfaceInfo, errors.NotSupportedf( 1096 "address allocation on any available subnets is", 1097 ) 1098 } 1099 subnet, err := p.createOrFetchStateSubnet(subnetInfo) 1100 1101 return subnet, subnetInfo, interfaceInfo, nil 1102 } 1103 1104 // These are defined like this to allow mocking in tests. 1105 var ( 1106 allocateAddrTo = func(a *state.IPAddress, m *state.Machine, macAddress string) error { 1107 // TODO(mfoord): populate proper interface ID (in state). 1108 return a.AllocateTo(m.Id(), "", macAddress) 1109 } 1110 setAddrsTo = func(a *state.IPAddress, m *state.Machine) error { 1111 return m.SetProviderAddresses(a.Address()) 1112 } 1113 setAddrState = func(a *state.IPAddress, st state.AddressState) error { 1114 return a.SetState(st) 1115 } 1116 ) 1117 1118 // allocateAddress tries to pick an address out of the given subnet and 1119 // allocates it to the container. 1120 func (p *ProvisionerAPI) allocateAddress( 1121 environ environs.NetworkingEnviron, 1122 subnet *state.Subnet, 1123 host, container *state.Machine, 1124 instId instance.Id, 1125 macAddress string, 1126 ) (*state.IPAddress, error) { 1127 hostname := containerHostname(container.Tag()) 1128 1129 if !environs.AddressAllocationEnabled(environ.Config().Type()) { 1130 // Even if the address allocation feature flag is not enabled, we might 1131 // be running on MAAS 1.8+ with devices support, which we can use to 1132 // register containers getting IPs via DHCP. However, most of the usual 1133 // allocation code can be bypassed, we just need the parent instance ID 1134 // and a MAC address (no subnet or IP address). 1135 allocatedAddress := network.Address{} 1136 err := environ.AllocateAddress(instId, network.AnySubnet, &allocatedAddress, macAddress, hostname) 1137 if err != nil { 1138 // Not using MAAS 1.8+ or some other error. 1139 return nil, errors.Trace(err) 1140 } 1141 1142 logger.Infof( 1143 "allocated address %q on instance %q for container %q", 1144 allocatedAddress.String(), instId, hostname, 1145 ) 1146 1147 // Add the address to state, so we can look it up later by MAC address. 1148 stateAddr, err := p.st.AddIPAddress(allocatedAddress, string(network.AnySubnet)) 1149 if err != nil { 1150 return nil, errors.Annotatef(err, "failed to save address %q", allocatedAddress) 1151 } 1152 1153 err = p.setAllocatedOrRelease(stateAddr, environ, instId, container, network.AnySubnet, macAddress) 1154 if err != nil { 1155 return nil, errors.Trace(err) 1156 } 1157 1158 return stateAddr, nil 1159 } 1160 1161 subnetId := network.Id(subnet.ProviderId()) 1162 for { 1163 addr, err := subnet.PickNewAddress() 1164 if err != nil { 1165 return nil, err 1166 } 1167 netAddr := addr.Address() 1168 logger.Tracef("picked new address %q on subnet %q", addr.String(), subnetId) 1169 // Attempt to allocate with environ. 1170 err = environ.AllocateAddress(instId, subnetId, &netAddr, macAddress, hostname) 1171 if err != nil { 1172 logger.Warningf( 1173 "allocating address %q on instance %q and subnet %q failed: %v (retrying)", 1174 addr.String(), instId, subnetId, err, 1175 ) 1176 // It's as good as unavailable for us, so mark it as 1177 // such. 1178 err = setAddrState(addr, state.AddressStateUnavailable) 1179 if err != nil { 1180 logger.Warningf( 1181 "cannot set address %q to %q: %v (ignoring and retrying)", 1182 addr.String(), state.AddressStateUnavailable, err, 1183 ) 1184 continue 1185 } 1186 logger.Tracef( 1187 "setting address %q to %q and retrying", 1188 addr.String(), state.AddressStateUnavailable, 1189 ) 1190 continue 1191 } 1192 logger.Infof( 1193 "allocated address %q on instance %q and subnet %q", 1194 addr.String(), instId, subnetId, 1195 ) 1196 err = p.setAllocatedOrRelease(addr, environ, instId, container, subnetId, macAddress) 1197 if err != nil { 1198 // Something went wrong - retry. 1199 continue 1200 } 1201 return addr, nil 1202 } 1203 } 1204 1205 // setAllocatedOrRelease tries to associate the newly allocated 1206 // address addr with the container. On failure it makes the best 1207 // effort to cleanup and release addr, logging issues along the way. 1208 func (p *ProvisionerAPI) setAllocatedOrRelease( 1209 addr *state.IPAddress, 1210 environ environs.NetworkingEnviron, 1211 instId instance.Id, 1212 container *state.Machine, 1213 subnetId network.Id, 1214 macAddress string, 1215 ) (err error) { 1216 defer func() { 1217 if errors.Cause(err) == nil { 1218 // Success! 1219 return 1220 } 1221 logger.Warningf( 1222 "failed to mark address %q as %q to container %q: %v (releasing and retrying)", 1223 addr.String(), state.AddressStateAllocated, container, err, 1224 ) 1225 // It's as good as unavailable for us, so mark it as 1226 // such. 1227 err = setAddrState(addr, state.AddressStateUnavailable) 1228 if err != nil { 1229 logger.Warningf( 1230 "cannot set address %q to %q: %v (ignoring and releasing)", 1231 addr.String(), state.AddressStateUnavailable, err, 1232 ) 1233 } 1234 err = environ.ReleaseAddress(instId, subnetId, addr.Address(), addr.MACAddress(), "") 1235 if err == nil { 1236 logger.Infof("address %q released; trying to allocate new", addr.String()) 1237 return 1238 } 1239 logger.Warningf( 1240 "failed to release address %q on instance %q and subnet %q: %v (ignoring and retrying)", 1241 addr.String(), instId, subnetId, err, 1242 ) 1243 }() 1244 1245 // Any errors returned below will trigger the release/cleanup 1246 // steps above. 1247 if err = allocateAddrTo(addr, container, macAddress); err != nil { 1248 return errors.Trace(err) 1249 } 1250 if err = setAddrsTo(addr, container); err != nil { 1251 return errors.Trace(err) 1252 } 1253 1254 logger.Infof("assigned address %q to container %q", addr.String(), container) 1255 return nil 1256 } 1257 1258 func (p *ProvisionerAPI) createOrFetchStateSubnet(subnetInfo network.SubnetInfo) (*state.Subnet, error) { 1259 stateSubnetInfo := state.SubnetInfo{ 1260 ProviderId: subnetInfo.ProviderId, 1261 CIDR: subnetInfo.CIDR, 1262 VLANTag: subnetInfo.VLANTag, 1263 AllocatableIPHigh: subnetInfo.AllocatableIPHigh.String(), 1264 AllocatableIPLow: subnetInfo.AllocatableIPLow.String(), 1265 } 1266 subnet, err := p.st.AddSubnet(stateSubnetInfo) 1267 if err != nil { 1268 if errors.IsAlreadyExists(err) { 1269 subnet, err = p.st.Subnet(subnetInfo.CIDR) 1270 } 1271 if err != nil { 1272 return subnet, errors.Trace(err) 1273 } 1274 } 1275 return subnet, nil 1276 } 1277 1278 // InstanceStatus returns the instance status for each given entity. 1279 // Only machine tags are accepted. 1280 func (p *ProvisionerAPI) InstanceStatus(args params.Entities) (params.StatusResults, error) { 1281 result := params.StatusResults{ 1282 Results: make([]params.StatusResult, len(args.Entities)), 1283 } 1284 canAccess, err := p.getAuthFunc() 1285 if err != nil { 1286 logger.Errorf("failed to get an authorisation function: %v", err) 1287 return result, errors.Trace(err) 1288 } 1289 for i, arg := range args.Entities { 1290 mTag, err := names.ParseMachineTag(arg.Tag) 1291 if err != nil { 1292 result.Results[i].Error = common.ServerError(err) 1293 continue 1294 } 1295 machine, err := p.getMachine(canAccess, mTag) 1296 if err == nil { 1297 var statusInfo status.StatusInfo 1298 statusInfo, err = machine.InstanceStatus() 1299 result.Results[i].Status = statusInfo.Status 1300 result.Results[i].Info = statusInfo.Message 1301 result.Results[i].Data = statusInfo.Data 1302 result.Results[i].Since = statusInfo.Since 1303 } 1304 result.Results[i].Error = common.ServerError(err) 1305 } 1306 return result, nil 1307 } 1308 1309 // SetInstanceStatus updates the instance status for each given 1310 // entity. Only machine tags are accepted. 1311 func (p *ProvisionerAPI) SetInstanceStatus(args params.SetStatus) (params.ErrorResults, error) { 1312 result := params.ErrorResults{ 1313 Results: make([]params.ErrorResult, len(args.Entities)), 1314 } 1315 canAccess, err := p.getAuthFunc() 1316 if err != nil { 1317 logger.Errorf("failed to get an authorisation function: %v", err) 1318 return result, errors.Trace(err) 1319 } 1320 for i, arg := range args.Entities { 1321 mTag, err := names.ParseMachineTag(arg.Tag) 1322 if err != nil { 1323 result.Results[i].Error = common.ServerError(err) 1324 continue 1325 } 1326 machine, err := p.getMachine(canAccess, mTag) 1327 if err == nil { 1328 err = machine.SetInstanceStatus(arg.Status, arg.Info, arg.Data) 1329 } 1330 result.Results[i].Error = common.ServerError(err) 1331 } 1332 return result, nil 1333 }