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 &params.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 = &params.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  }