1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"github.com/juju/errors"
12
"github.com/juju/utils/series"
13
"github.com/juju/utils/set"
14
"gopkg.in/juju/names.v2"
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"
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)),
35
canAccess, err := p.getAuthFunc()
39
for i, entity := range args.Entities {
40
tag, err := names.ParseMachineTag(entity.Tag)
42
result.Results[i].Error = common.ServerError(common.ErrPerm)
45
machine, err := p.getMachine(canAccess, tag)
47
result.Results[i].Result, err = p.getProvisioningInfo(machine)
49
result.Results[i].Error = common.ServerError(err)
54
func (p *ProvisionerAPI) getProvisioningInfo(m *state.Machine) (*params.ProvisioningInfo, error) {
55
cons, err := m.Constraints()
60
volumes, err := p.machineVolumeParams(m)
62
return nil, errors.Trace(err)
65
var jobs []multiwatcher.MachineJob
66
for _, job := range m.Jobs() {
67
jobs = append(jobs, job.ToParams())
70
tags, err := p.machineTags(m, jobs)
72
return nil, errors.Trace(err)
75
subnetsToZones, err := p.machineSubnetsAndZones(m)
77
return nil, errors.Annotate(err, "cannot match subnets to zones")
80
endpointBindings, err := p.machineEndpointBindings(m)
82
return nil, errors.Annotate(err, "cannot determine machine endpoint bindings")
84
imageMetadata, err := p.availableImageMetadata(m)
86
return nil, errors.Annotate(err, "cannot get available image metadata")
88
controllerCfg, err := p.st.ControllerConfig()
90
return nil, errors.Annotate(err, "cannot get controller configuration")
93
return ¶ms.ProvisioningInfo{
96
Placement: m.Placement(),
100
SubnetsToZones: subnetsToZones,
101
EndpointBindings: endpointBindings,
102
ImageMetadata: imageMetadata,
103
ControllerConfig: controllerCfg,
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()
115
if len(volumeAttachments) == 0 {
118
modelConfig, err := p.st.ModelConfig()
122
controllerCfg, err := p.st.ControllerConfig()
126
allVolumeParams := make([]params.VolumeParams, 0, len(volumeAttachments))
127
for _, volumeAttachment := range volumeAttachments {
128
volumeTag := volumeAttachment.Volume()
129
volume, err := p.st.Volume(volumeTag)
131
return nil, errors.Annotatef(err, "getting volume %q", volumeTag.Id())
133
storageInstance, err := storagecommon.MaybeAssignedStorageInstance(
134
volume.StorageInstance, p.st.StorageInstance,
137
return nil, errors.Annotatef(err, "getting volume %q storage instance", volumeTag.Id())
139
volumeParams, err := storagecommon.VolumeParams(
140
volume, storageInstance, modelConfig.UUID(), controllerCfg.ControllerUUID(),
141
modelConfig, p.storagePoolManager, p.storageProviderRegistry,
144
return nil, errors.Annotatef(err, "getting volume %q parameters", volumeTag.Id())
146
provider, err := p.storageProviderRegistry.StorageProvider(storage.ProviderType(volumeParams.Provider))
148
return nil, errors.Annotate(err, "getting storage provider")
150
if provider.Dynamic() {
151
// Leave dynamic storage to the storage provisioner.
154
volumeAttachmentParams, ok := volumeAttachment.Params()
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(),
163
// Not provisioned yet, so ask the cloud provisioner do it.
164
volumeParams.Attachment = ¶ms.VolumeAttachmentParams{
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,
172
allVolumeParams = append(allVolumeParams, volumeParams)
174
return allVolumeParams, nil
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.
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()
186
return nil, errors.Trace(err)
188
unitNames := make([]string, 0, len(units))
189
for _, unit := range units {
190
if !unit.IsPrincipal() {
193
unitNames = append(unitNames, unit.Name())
195
sort.Strings(unitNames)
197
cfg, err := p.st.ModelConfig()
199
return nil, errors.Trace(err)
201
controllerCfg, err := p.st.ControllerConfig()
203
return nil, errors.Trace(err)
205
machineTags := instancecfg.InstanceTags(cfg.UUID(), controllerCfg.ControllerUUID(), cfg, jobs)
206
if len(unitNames) > 0 {
207
machineTags[tags.JujuUnitsDeployed] = strings.Join(unitNames, " ")
209
return machineTags, nil
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()
219
return nil, errors.Annotate(err, "cannot get machine constraints")
221
includeSpaces := mcons.IncludeSpaces()
222
if len(includeSpaces) < 1 {
226
// TODO(dimitern): For the network model MVP we only use the first
227
// included space and ignore the rest.
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 {
234
"using space %q from constraints for machine %q (ignoring remaining: %v)",
235
spaceName, m.Id(), includeSpaces[1:],
238
space, err := p.st.Space(spaceName)
240
return nil, errors.Trace(err)
242
subnets, err := space.Subnets()
244
return nil, errors.Trace(err)
246
if len(subnets) == 0 {
247
return nil, errors.Errorf("cannot use space %q as deployment target: no subnets", spaceName)
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(),
255
providerId := subnet.ProviderId()
256
if providerId == "" {
257
logger.Warningf(warningPrefix + "no ProviderId set")
260
// TODO(dimitern): Once state.Subnet supports multiple zones,
261
// use all of them below.
263
// LKK Card: https://canonical.leankit.com/Boards/View/101652562/119979611
264
zone := subnet.AvailabilityZone()
266
logger.Warningf(warningPrefix + "no availability zone(s) set")
269
subnetsToZones[string(providerId)] = []string{zone}
271
return subnetsToZones, nil
274
func (p *ProvisionerAPI) machineEndpointBindings(m *state.Machine) (map[string]string, error) {
275
units, err := m.Units()
277
return nil, errors.Trace(err)
280
spacesNamesToProviderIds, err := p.allSpaceNamesToProviderIds()
282
return nil, errors.Trace(err)
285
var combinedBindings map[string]string
286
processedServicesSet := set.NewStrings()
287
for _, unit := range units {
288
if !unit.IsPrincipal() {
291
service, err := unit.Application()
295
if processedServicesSet.Contains(service.Name()) {
296
// Already processed, skip it.
299
bindings, err := service.EndpointBindings()
303
processedServicesSet.Add(service.Name())
305
if len(bindings) == 0 {
308
if combinedBindings == nil {
309
combinedBindings = make(map[string]string)
312
for endpoint, spaceName := range bindings {
314
// Skip unspecified bindings, as they won't affect the instance
315
// selected for provisioning.
319
spaceProviderId, nameKnown := spacesNamesToProviderIds[spaceName]
321
combinedBindings[endpoint] = spaceProviderId
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)
329
return combinedBindings, nil
332
func (p *ProvisionerAPI) allSpaceNamesToProviderIds() (map[string]string, error) {
333
allSpaces, err := p.st.AllSpaces()
335
return nil, errors.Annotate(err, "getting all spaces")
338
namesToProviderIds := make(map[string]string, len(allSpaces))
339
for _, space := range allSpaces {
342
// For providers without native support for spaces, use the name instead
344
providerId := string(space.ProviderId())
345
if len(providerId) == 0 {
349
namesToProviderIds[name] = providerId
352
return namesToProviderIds, nil
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)
360
return nil, errors.Annotate(err, "could not construct image constraint")
363
// Look for image metadata in state.
364
data, err := p.findImageMetadata(imageConstraint, env)
368
sort.Sort(metadataList(data))
369
logger.Debugf("available image metadata for provisioning: %v", data)
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()
379
return nil, nil, errors.Trace(err)
382
lookup := simplestreams.LookupParams{
383
Series: []string{m.Series()},
384
Stream: env.Config().ImageStream(),
387
mcons, err := m.Constraints()
389
return nil, nil, errors.Annotatef(err, "cannot get machine constraints for machine %v", m.MachineTag().Id())
392
if mcons.Arch != nil {
393
lookup.Arches = []string{*mcons.Arch}
396
lookup.CloudSpec = *cloud
399
return imagemetadata.NewImageConstraint(lookup), env, nil
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)
407
return nil, nil, errors.Annotate(err, "could not get model")
410
if inst, ok := env.(simplestreams.HasRegion); ok {
411
cloud, err := inst.Region()
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)")
417
return &cloud, env, nil
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)
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
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)
445
if !errors.IsNotFound(err) {
446
return nil, errors.Trace(err)
449
logger.Debugf("got from data sources %d metadata", len(dsMetadata))
451
return dsMetadata, nil
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,
463
stored, err := p.st.CloudImageMetadataStorage.FindMetadata(filter)
465
return nil, errors.Trace(err)
468
toParams := func(m cloudimagemetadata.Metadata) params.CloudImageMetadata {
469
return params.CloudImageMetadata{
476
VirtType: m.VirtType,
477
RootStorageType: m.RootStorageType,
478
RootStorageSize: m.RootStorageSize,
480
Priority: m.Priority,
484
var all []params.CloudImageMetadata
485
for _, ms := range stored {
486
for _, m := range ms {
487
all = append(all, toParams(m))
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)
500
getStream := func(current string) string {
502
if constraint.Stream != "" {
503
return constraint.Stream
505
return env.Config().ImageStream()
510
toModel := func(m *imagemetadata.ImageMetadata, mStream string, mSeries string, source string, priority int) cloudimagemetadata.Metadata {
512
return cloudimagemetadata.Metadata{
513
cloudimagemetadata.MetadataAttributes{
514
Region: m.RegionName,
516
VirtType: m.VirtType,
517
RootStorageType: m.Storage,
527
var metadataState []cloudimagemetadata.Metadata
528
for _, source := range sources {
529
logger.Debugf("looking in data source %v", source.Description())
530
found, info, err := imagemetadata.Fetch([]simplestreams.DataSource{source}, constraint)
532
// Do not stop looking in other data sources if there is an issue here.
533
logger.Warningf("encountered %v while getting published images metadata from %v", err, source.Description())
536
for _, m := range found {
537
mSeries, err := series.VersionSeries(m.Version)
539
logger.Warningf("could not determine series for image id %s: %v", m.Id, err)
542
mStream := getStream(m.Stream)
543
metadataState = append(metadataState, toModel(m, mStream, mSeries, info.Source, source.Priority()))
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)
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)
557
return nil, errors.Annotate(err, "could not read metadata from controller after saving it there from data sources")
561
return nil, errors.NotFoundf("image metadata for series %v, arch %v", constraint.Series, constraint.Arches)
567
// metadataList is a convenience type enabling to sort
568
// a collection of CloudImageMetadata in order of priority.
569
type metadataList []params.CloudImageMetadata
571
// Implements sort.Interface
572
func (m metadataList) Len() int {
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
581
// Implements sort.Interface
582
func (m metadataList) Swap(i, j int) {
583
m[i], m[j] = m[j], m[i]