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