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