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