github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/provisioner/provisioninginfo.go (about) 1 // Copyright 2016 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/collections/set" 12 "github.com/juju/errors" 13 "github.com/juju/os/series" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/apiserver/common" 17 "github.com/juju/juju/apiserver/common/storagecommon" 18 "github.com/juju/juju/apiserver/params" 19 "github.com/juju/juju/cloudconfig/instancecfg" 20 "github.com/juju/juju/core/lxdprofile" 21 "github.com/juju/juju/environs" 22 "github.com/juju/juju/environs/imagemetadata" 23 "github.com/juju/juju/environs/simplestreams" 24 "github.com/juju/juju/environs/tags" 25 "github.com/juju/juju/state" 26 "github.com/juju/juju/state/cloudimagemetadata" 27 "github.com/juju/juju/state/multiwatcher" 28 "github.com/juju/juju/storage" 29 ) 30 31 // ProvisioningInfo returns the provisioning information for each given machine entity. 32 func (p *ProvisionerAPI) ProvisioningInfo(args params.Entities) (params.ProvisioningInfoResults, error) { 33 result := params.ProvisioningInfoResults{ 34 Results: make([]params.ProvisioningInfoResult, len(args.Entities)), 35 } 36 canAccess, err := p.getAuthFunc() 37 if err != nil { 38 return result, errors.Trace(err) 39 } 40 env, err := environs.GetEnviron(p.configGetter, environs.New) 41 if err != nil { 42 return result, errors.Annotate(err, "could not get environ") 43 } 44 for i, entity := range args.Entities { 45 tag, err := names.ParseMachineTag(entity.Tag) 46 if err != nil { 47 result.Results[i].Error = common.ServerError(common.ErrPerm) 48 continue 49 } 50 machine, err := p.getMachine(canAccess, tag) 51 if err == nil { 52 result.Results[i].Result, err = p.getProvisioningInfo(machine, env) 53 } 54 result.Results[i].Error = common.ServerError(err) 55 } 56 return result, nil 57 } 58 59 func (p *ProvisionerAPI) getProvisioningInfo(m *state.Machine, env environs.Environ) (*params.ProvisioningInfo, error) { 60 cons, err := m.Constraints() 61 if err != nil { 62 return nil, errors.Trace(err) 63 } 64 65 volumes, volumeAttachments, err := p.machineVolumeParams(m, env) 66 if err != nil { 67 return nil, errors.Trace(err) 68 } 69 70 var jobs []multiwatcher.MachineJob 71 for _, job := range m.Jobs() { 72 jobs = append(jobs, job.ToParams()) 73 } 74 75 tags, err := p.machineTags(m, jobs) 76 if err != nil { 77 return nil, errors.Trace(err) 78 } 79 80 subnetsToZones, err := p.machineSubnetsAndZones(m) 81 if err != nil { 82 return nil, errors.Annotate(err, "cannot match subnets to zones") 83 } 84 85 pNames, err := p.machineLXDProfileNames(m, env) 86 if err != nil { 87 return nil, errors.Annotate(err, "cannot write lxd profiles") 88 } 89 90 endpointBindings, err := p.machineEndpointBindings(m) 91 if err != nil { 92 return nil, errors.Annotate(err, "cannot determine machine endpoint bindings") 93 } 94 95 imageMetadata, err := p.availableImageMetadata(m, env) 96 if err != nil { 97 return nil, errors.Annotate(err, "cannot get available image metadata") 98 } 99 100 controllerCfg, err := p.st.ControllerConfig() 101 if err != nil { 102 return nil, errors.Annotate(err, "cannot get controller configuration") 103 } 104 105 return ¶ms.ProvisioningInfo{ 106 Constraints: cons, 107 Series: m.Series(), 108 Placement: m.Placement(), 109 Jobs: jobs, 110 Volumes: volumes, 111 VolumeAttachments: volumeAttachments, 112 Tags: tags, 113 SubnetsToZones: subnetsToZones, 114 EndpointBindings: endpointBindings, 115 ImageMetadata: imageMetadata, 116 ControllerConfig: controllerCfg, 117 CloudInitUserData: env.Config().CloudInitUserData(), 118 CharmLXDProfiles: pNames, 119 }, nil 120 } 121 122 // machineVolumeParams retrieves VolumeParams for the volumes that should be 123 // provisioned with, and attached to, the machine. The client should ignore 124 // parameters that it does not know how to handle. 125 func (p *ProvisionerAPI) machineVolumeParams( 126 m *state.Machine, 127 env environs.Environ, 128 ) ([]params.VolumeParams, []params.VolumeAttachmentParams, error) { 129 sb, err := state.NewStorageBackend(p.st) 130 if err != nil { 131 return nil, nil, errors.Trace(err) 132 } 133 volumeAttachments, err := m.VolumeAttachments() 134 if err != nil { 135 return nil, nil, errors.Trace(err) 136 } 137 if len(volumeAttachments) == 0 { 138 return nil, nil, nil 139 } 140 modelConfig, err := p.m.ModelConfig() 141 if err != nil { 142 return nil, nil, errors.Trace(err) 143 } 144 controllerCfg, err := p.st.ControllerConfig() 145 if err != nil { 146 return nil, nil, errors.Trace(err) 147 } 148 allVolumeParams := make([]params.VolumeParams, 0, len(volumeAttachments)) 149 var allVolumeAttachmentParams []params.VolumeAttachmentParams 150 for _, volumeAttachment := range volumeAttachments { 151 volumeTag := volumeAttachment.Volume() 152 volume, err := sb.Volume(volumeTag) 153 if err != nil { 154 return nil, nil, errors.Annotatef(err, "getting volume %q", volumeTag.Id()) 155 } 156 storageInstance, err := storagecommon.MaybeAssignedStorageInstance( 157 volume.StorageInstance, sb.StorageInstance, 158 ) 159 if err != nil { 160 return nil, nil, errors.Annotatef(err, "getting volume %q storage instance", volumeTag.Id()) 161 } 162 volumeParams, err := storagecommon.VolumeParams( 163 volume, storageInstance, modelConfig.UUID(), controllerCfg.ControllerUUID(), 164 modelConfig, p.storagePoolManager, p.storageProviderRegistry, 165 ) 166 if err != nil { 167 return nil, nil, errors.Annotatef(err, "getting volume %q parameters", volumeTag.Id()) 168 } 169 if _, err := env.StorageProvider(storage.ProviderType(volumeParams.Provider)); errors.IsNotFound(err) { 170 // This storage type is not managed by the environ 171 // provider, so ignore it. It'll be managed by one 172 // of the storage provisioners. 173 continue 174 } else if err != nil { 175 return nil, nil, errors.Annotate(err, "getting storage provider") 176 } 177 178 var volumeProvisioned bool 179 volumeInfo, err := volume.Info() 180 if err == nil { 181 volumeProvisioned = true 182 } else if !errors.IsNotProvisioned(err) { 183 return nil, nil, errors.Annotate(err, "getting volume info") 184 } 185 stateVolumeAttachmentParams, volumeDetached := volumeAttachment.Params() 186 if !volumeDetached { 187 // Volume is already attached to the machine, so 188 // there's nothing more to do for it. 189 continue 190 } 191 volumeAttachmentParams := params.VolumeAttachmentParams{ 192 volumeTag.String(), 193 m.Tag().String(), 194 volumeInfo.VolumeId, 195 "", // we're creating the machine, so it has no instance ID. 196 volumeParams.Provider, 197 stateVolumeAttachmentParams.ReadOnly, 198 } 199 if volumeProvisioned { 200 // Volume is already provisioned, so we just need to attach it. 201 allVolumeAttachmentParams = append( 202 allVolumeAttachmentParams, volumeAttachmentParams, 203 ) 204 } else { 205 // Not provisioned yet, so ask the cloud provisioner do it. 206 volumeParams.Attachment = &volumeAttachmentParams 207 allVolumeParams = append(allVolumeParams, volumeParams) 208 } 209 } 210 return allVolumeParams, allVolumeAttachmentParams, nil 211 } 212 213 // machineTags returns machine-specific tags to set on the instance. 214 func (p *ProvisionerAPI) machineTags(m *state.Machine, jobs []multiwatcher.MachineJob) (map[string]string, error) { 215 // Names of all units deployed to the machine. 216 // 217 // TODO(axw) 2015-06-02 #1461358 218 // We need a worker that periodically updates 219 // instance tags with current deployment info. 220 units, err := m.Units() 221 if err != nil { 222 return nil, errors.Trace(err) 223 } 224 unitNames := make([]string, 0, len(units)) 225 for _, unit := range units { 226 if !unit.IsPrincipal() { 227 continue 228 } 229 unitNames = append(unitNames, unit.Name()) 230 } 231 sort.Strings(unitNames) 232 233 cfg, err := p.m.ModelConfig() 234 if err != nil { 235 return nil, errors.Trace(err) 236 } 237 controllerCfg, err := p.st.ControllerConfig() 238 if err != nil { 239 return nil, errors.Trace(err) 240 } 241 machineTags := instancecfg.InstanceTags(cfg.UUID(), controllerCfg.ControllerUUID(), cfg, jobs) 242 if len(unitNames) > 0 { 243 machineTags[tags.JujuUnitsDeployed] = strings.Join(unitNames, " ") 244 } 245 machineId := fmt.Sprintf("%s-%s", cfg.Name(), m.Tag().String()) 246 machineTags[tags.JujuMachine] = machineId 247 return machineTags, nil 248 } 249 250 // machineSubnetsAndZones returns a map of subnet provider-specific id 251 // to list of availability zone names for that subnet. The result can 252 // be empty if there are no spaces constraints specified for the 253 // machine, or there's an error fetching them. 254 func (p *ProvisionerAPI) machineSubnetsAndZones(m *state.Machine) (map[string][]string, error) { 255 mcons, err := m.Constraints() 256 if err != nil { 257 return nil, errors.Annotate(err, "cannot get machine constraints") 258 } 259 includeSpaces := mcons.IncludeSpaces() 260 if len(includeSpaces) < 1 { 261 // Nothing to do. 262 return nil, nil 263 } 264 // TODO(dimitern): For the network model MVP we only use the first 265 // included space and ignore the rest. 266 // 267 // LKK Card: https://canonical.leankit.com/Boards/View/101652562/117352306 268 // LP Bug: http://pad.lv/1498232 269 spaceName := includeSpaces[0] 270 if len(includeSpaces) > 1 { 271 logger.Debugf( 272 "using space %q from constraints for machine %q (ignoring remaining: %v)", 273 spaceName, m.Id(), includeSpaces[1:], 274 ) 275 } 276 space, err := p.st.Space(spaceName) 277 if err != nil { 278 return nil, errors.Trace(err) 279 } 280 subnets, err := space.Subnets() 281 if err != nil { 282 return nil, errors.Trace(err) 283 } 284 if len(subnets) == 0 { 285 return nil, errors.Errorf("cannot use space %q as deployment target: no subnets", spaceName) 286 } 287 subnetsToZones := make(map[string][]string, len(subnets)) 288 for _, subnet := range subnets { 289 warningPrefix := fmt.Sprintf( 290 "not using subnet %q in space %q for machine %q provisioning: ", 291 subnet.CIDR(), spaceName, m.Id(), 292 ) 293 providerId := subnet.ProviderId() 294 if providerId == "" { 295 logger.Warningf(warningPrefix + "no ProviderId set") 296 continue 297 } 298 // TODO(dimitern): Once state.Subnet supports multiple zones, 299 // use all of them below. 300 // 301 // LKK Card: https://canonical.leankit.com/Boards/View/101652562/119979611 302 zone := subnet.AvailabilityZone() 303 if zone == "" { 304 logger.Warningf(warningPrefix + "no availability zone(s) set") 305 continue 306 } 307 subnetsToZones[string(providerId)] = []string{zone} 308 } 309 return subnetsToZones, nil 310 } 311 312 // machineLXDProfileNames give the environ info to write lxd profiles needed for 313 // the given machine and returns the names of profiles. Unlike 314 // containerLXDProfilesInfo which returns the info necessary to write lxd profiles 315 // via the lxd broker. 316 func (p *ProvisionerAPI) machineLXDProfileNames(m *state.Machine, env environs.Environ) ([]string, error) { 317 profileEnv, ok := env.(environs.LXDProfiler) 318 if !ok { 319 logger.Tracef("LXDProfiler not implemented by environ") 320 return nil, nil 321 } 322 units, err := m.Units() 323 if err != nil { 324 return nil, errors.Trace(err) 325 } 326 var names []string 327 for _, unit := range units { 328 app, err := unit.Application() 329 if err != nil { 330 return nil, errors.Trace(err) 331 } 332 ch, _, err := app.Charm() 333 if err != nil { 334 return nil, errors.Trace(err) 335 } 336 profile := ch.LXDProfile() 337 if profile == nil || (profile != nil && profile.Empty()) { 338 continue 339 } 340 pName := lxdprofile.Name(p.m.Name(), app.Name(), ch.Revision()) 341 if err := profileEnv.MaybeWriteLXDProfile(pName, profile); err != nil { 342 return nil, errors.Trace(err) 343 } 344 names = append(names, pName) 345 } 346 return names, nil 347 } 348 349 func (p *ProvisionerAPI) machineEndpointBindings(m *state.Machine) (map[string]string, error) { 350 units, err := m.Units() 351 if err != nil { 352 return nil, errors.Trace(err) 353 } 354 355 spacesNamesToProviderIds, err := p.allSpaceNamesToProviderIds() 356 if err != nil { 357 return nil, errors.Trace(err) 358 } 359 360 var combinedBindings map[string]string 361 processedServicesSet := set.NewStrings() 362 for _, unit := range units { 363 if !unit.IsPrincipal() { 364 continue 365 } 366 service, err := unit.Application() 367 if err != nil { 368 return nil, errors.Trace(err) 369 } 370 if processedServicesSet.Contains(service.Name()) { 371 // Already processed, skip it. 372 continue 373 } 374 bindings, err := service.EndpointBindings() 375 if err != nil { 376 return nil, errors.Trace(err) 377 } 378 processedServicesSet.Add(service.Name()) 379 380 if len(bindings) == 0 { 381 continue 382 } 383 if combinedBindings == nil { 384 combinedBindings = make(map[string]string) 385 } 386 387 for endpoint, spaceName := range bindings { 388 if spaceName == "" { 389 // Skip unspecified bindings, as they won't affect the instance 390 // selected for provisioning. 391 continue 392 } 393 394 spaceProviderId, nameKnown := spacesNamesToProviderIds[spaceName] 395 if nameKnown { 396 combinedBindings[endpoint] = spaceProviderId 397 } else { 398 // Technically, this can't happen in practice, as we're 399 // validating the bindings during service deployment. 400 return nil, errors.Errorf("unknown space %q with no provider ID specified for endpoint %q", spaceName, endpoint) 401 } 402 } 403 } 404 return combinedBindings, nil 405 } 406 407 func (p *ProvisionerAPI) allSpaceNamesToProviderIds() (map[string]string, error) { 408 allSpaces, err := p.st.AllSpaces() 409 if err != nil { 410 return nil, errors.Annotate(err, "getting all spaces") 411 } 412 413 namesToProviderIds := make(map[string]string, len(allSpaces)) 414 for _, space := range allSpaces { 415 name := space.Name() 416 417 // For providers without native support for spaces, use the name instead 418 // as provider ID. 419 providerId := string(space.ProviderId()) 420 if len(providerId) == 0 { 421 providerId = name 422 } 423 424 namesToProviderIds[name] = providerId 425 } 426 427 return namesToProviderIds, nil 428 } 429 430 // availableImageMetadata returns all image metadata available to this machine 431 // or an error fetching them. 432 func (p *ProvisionerAPI) availableImageMetadata(m *state.Machine, env environs.Environ) ([]params.CloudImageMetadata, error) { 433 imageConstraint, err := p.constructImageConstraint(m, env) 434 if err != nil { 435 return nil, errors.Annotate(err, "could not construct image constraint") 436 } 437 438 // Look for image metadata in state. 439 data, err := p.findImageMetadata(imageConstraint, env) 440 if err != nil { 441 return nil, errors.Trace(err) 442 } 443 sort.Sort(metadataList(data)) 444 logger.Debugf("available image metadata for provisioning: %v", data) 445 return data, nil 446 } 447 448 // constructImageConstraint returns model-specific criteria used to look for image metadata. 449 func (p *ProvisionerAPI) constructImageConstraint(m *state.Machine, env environs.Environ) (*imagemetadata.ImageConstraint, error) { 450 lookup := simplestreams.LookupParams{ 451 Series: []string{m.Series()}, 452 Stream: env.Config().ImageStream(), 453 } 454 455 mcons, err := m.Constraints() 456 if err != nil { 457 return nil, errors.Annotatef(err, "cannot get machine constraints for machine %v", m.MachineTag().Id()) 458 } 459 460 if mcons.Arch != nil { 461 lookup.Arches = []string{*mcons.Arch} 462 } 463 464 if hasRegion, ok := env.(simplestreams.HasRegion); ok { 465 // We can determine current region; we want only 466 // metadata specific to this region. 467 spec, err := hasRegion.Region() 468 if err != nil { 469 // can't really find images if we cannot determine cloud region 470 // TODO (anastasiamac 2015-12-03) or can we? 471 return nil, errors.Annotate(err, "getting provider region information (cloud spec)") 472 } 473 lookup.CloudSpec = spec 474 } 475 476 return imagemetadata.NewImageConstraint(lookup), nil 477 } 478 479 // findImageMetadata returns all image metadata or an error fetching them. 480 // It looks for image metadata in state. 481 // If none are found, we fall back on original image search in simple streams. 482 func (p *ProvisionerAPI) findImageMetadata(imageConstraint *imagemetadata.ImageConstraint, env environs.Environ) ([]params.CloudImageMetadata, error) { 483 // Look for image metadata in state. 484 stateMetadata, err := p.imageMetadataFromState(imageConstraint) 485 if err != nil && !errors.IsNotFound(err) { 486 // look into simple stream if for some reason can't get from controller, 487 // so do not exit on error. 488 logger.Infof("could not get image metadata from controller: %v", err) 489 } 490 logger.Debugf("got from controller %d metadata", len(stateMetadata)) 491 // No need to look in data sources if found in state. 492 if len(stateMetadata) != 0 { 493 return stateMetadata, nil 494 } 495 496 // If no metadata is found in state, fall back to original simple stream search. 497 // Currently, an image metadata worker picks up this metadata periodically (daily), 498 // and stores it in state. So potentially, this collection could be different 499 // to what is in state. 500 dsMetadata, err := p.imageMetadataFromDataSources(env, imageConstraint) 501 if err != nil { 502 if !errors.IsNotFound(err) { 503 return nil, errors.Trace(err) 504 } 505 } 506 logger.Debugf("got from data sources %d metadata", len(dsMetadata)) 507 508 return dsMetadata, nil 509 } 510 511 // imageMetadataFromState returns image metadata stored in state 512 // that matches given criteria. 513 func (p *ProvisionerAPI) imageMetadataFromState(constraint *imagemetadata.ImageConstraint) ([]params.CloudImageMetadata, error) { 514 filter := cloudimagemetadata.MetadataFilter{ 515 Series: constraint.Series, 516 Arches: constraint.Arches, 517 Region: constraint.Region, 518 Stream: constraint.Stream, 519 } 520 stored, err := p.st.CloudImageMetadataStorage.FindMetadata(filter) 521 if err != nil { 522 return nil, errors.Trace(err) 523 } 524 525 toParams := func(m cloudimagemetadata.Metadata) params.CloudImageMetadata { 526 return params.CloudImageMetadata{ 527 ImageId: m.ImageId, 528 Stream: m.Stream, 529 Region: m.Region, 530 Version: m.Version, 531 Series: m.Series, 532 Arch: m.Arch, 533 VirtType: m.VirtType, 534 RootStorageType: m.RootStorageType, 535 RootStorageSize: m.RootStorageSize, 536 Source: m.Source, 537 Priority: m.Priority, 538 } 539 } 540 541 var all []params.CloudImageMetadata 542 for _, ms := range stored { 543 for _, m := range ms { 544 all = append(all, toParams(m)) 545 } 546 } 547 return all, nil 548 } 549 550 // imageMetadataFromDataSources finds image metadata that match specified criteria in existing data sources. 551 func (p *ProvisionerAPI) imageMetadataFromDataSources(env environs.Environ, constraint *imagemetadata.ImageConstraint) ([]params.CloudImageMetadata, error) { 552 sources, err := environs.ImageMetadataSources(env) 553 if err != nil { 554 return nil, errors.Trace(err) 555 } 556 557 cfg := env.Config() 558 toModel := func(m *imagemetadata.ImageMetadata, mSeries string, source string, priority int) cloudimagemetadata.Metadata { 559 result := cloudimagemetadata.Metadata{ 560 MetadataAttributes: cloudimagemetadata.MetadataAttributes{ 561 Region: m.RegionName, 562 Arch: m.Arch, 563 VirtType: m.VirtType, 564 RootStorageType: m.Storage, 565 Source: source, 566 Series: mSeries, 567 Stream: m.Stream, 568 Version: m.Version, 569 }, 570 Priority: priority, 571 ImageId: m.Id, 572 } 573 // TODO (anastasiamac 2016-08-24) This is a band-aid solution. 574 // Once correct value is read from simplestreams, this needs to go. 575 // Bug# 1616295 576 if result.Stream == "" { 577 result.Stream = constraint.Stream 578 } 579 if result.Stream == "" { 580 result.Stream = cfg.ImageStream() 581 } 582 return result 583 } 584 585 var metadataState []cloudimagemetadata.Metadata 586 for _, source := range sources { 587 logger.Debugf("looking in data source %v", source.Description()) 588 found, info, err := imagemetadata.Fetch([]simplestreams.DataSource{source}, constraint) 589 if err != nil { 590 // Do not stop looking in other data sources if there is an issue here. 591 logger.Warningf("encountered %v while getting published images metadata from %v", err, source.Description()) 592 continue 593 } 594 for _, m := range found { 595 mSeries, err := series.VersionSeries(m.Version) 596 if err != nil { 597 logger.Warningf("could not determine series for image id %s: %v", m.Id, err) 598 continue 599 } 600 metadataState = append(metadataState, toModel(m, mSeries, info.Source, source.Priority())) 601 } 602 } 603 if len(metadataState) > 0 { 604 if err := p.st.CloudImageMetadataStorage.SaveMetadata(metadataState); err != nil { 605 // No need to react here, just take note 606 logger.Warningf("failed to save published image metadata: %v", err) 607 } 608 } 609 610 // Since we've fallen through to data sources search and have saved all needed images into controller, 611 // let's try to get them from controller to avoid duplication of conversion logic here. 612 all, err := p.imageMetadataFromState(constraint) 613 if err != nil { 614 return nil, errors.Annotate(err, "could not read metadata from controller after saving it there from data sources") 615 } 616 617 if len(all) == 0 { 618 return nil, errors.NotFoundf("image metadata for series %v, arch %v", constraint.Series, constraint.Arches) 619 } 620 621 return all, nil 622 } 623 624 // metadataList is a convenience type enabling to sort 625 // a collection of CloudImageMetadata in order of priority. 626 type metadataList []params.CloudImageMetadata 627 628 // Implements sort.Interface 629 func (m metadataList) Len() int { 630 return len(m) 631 } 632 633 // Implements sort.Interface and sorts image metadata by priority. 634 func (m metadataList) Less(i, j int) bool { 635 return m[i].Priority < m[j].Priority 636 } 637 638 // Implements sort.Interface 639 func (m metadataList) Swap(i, j int) { 640 m[i], m[j] = m[j], m[i] 641 }