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