github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/imagemetadata/metadata.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package imagemetadata
     5  
     6  import (
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/utils/series"
    13  
    14  	"github.com/juju/juju/apiserver/common"
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/environs/config"
    18  	envmetadata "github.com/juju/juju/environs/imagemetadata"
    19  	"github.com/juju/juju/environs/simplestreams"
    20  	"github.com/juju/juju/state"
    21  	"github.com/juju/juju/state/cloudimagemetadata"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.apiserver.imagemetadata")
    25  
    26  func init() {
    27  	common.RegisterStandardFacade("ImageMetadata", 2, NewAPI)
    28  }
    29  
    30  // API is the concrete implementation of the api end point
    31  // for loud image metadata manipulations.
    32  type API struct {
    33  	metadata   metadataAcess
    34  	authorizer common.Authorizer
    35  }
    36  
    37  // createAPI returns a new image metadata API facade.
    38  func createAPI(
    39  	st metadataAcess,
    40  	resources *common.Resources,
    41  	authorizer common.Authorizer,
    42  ) (*API, error) {
    43  	if !authorizer.AuthClient() && !authorizer.AuthModelManager() {
    44  		return nil, common.ErrPerm
    45  	}
    46  
    47  	return &API{
    48  		metadata:   st,
    49  		authorizer: authorizer,
    50  	}, nil
    51  }
    52  
    53  // NewAPI returns a new cloud image metadata API facade.
    54  func NewAPI(
    55  	st *state.State,
    56  	resources *common.Resources,
    57  	authorizer common.Authorizer,
    58  ) (*API, error) {
    59  	return createAPI(getState(st), resources, authorizer)
    60  }
    61  
    62  // List returns all found cloud image metadata that satisfy
    63  // given filter.
    64  // Returned list contains metadata ordered by priority.
    65  func (api *API) List(filter params.ImageMetadataFilter) (params.ListCloudImageMetadataResult, error) {
    66  	found, err := api.metadata.FindMetadata(cloudimagemetadata.MetadataFilter{
    67  		Region:          filter.Region,
    68  		Series:          filter.Series,
    69  		Arches:          filter.Arches,
    70  		Stream:          filter.Stream,
    71  		VirtType:        filter.VirtType,
    72  		RootStorageType: filter.RootStorageType,
    73  	})
    74  	if err != nil {
    75  		return params.ListCloudImageMetadataResult{}, common.ServerError(err)
    76  	}
    77  
    78  	var all []params.CloudImageMetadata
    79  	addAll := func(ms []cloudimagemetadata.Metadata) {
    80  		for _, m := range ms {
    81  			all = append(all, parseMetadataToParams(m))
    82  		}
    83  	}
    84  
    85  	for _, ms := range found {
    86  		addAll(ms)
    87  	}
    88  	sort.Sort(metadataList(all))
    89  
    90  	return params.ListCloudImageMetadataResult{Result: all}, nil
    91  }
    92  
    93  // Save stores given cloud image metadata.
    94  // It supports bulk calls.
    95  func (api *API) Save(metadata params.MetadataSaveParams) (params.ErrorResults, error) {
    96  	all := make([]params.ErrorResult, len(metadata.Metadata))
    97  	envCfg, err := api.metadata.ModelConfig()
    98  	if err != nil {
    99  		return params.ErrorResults{}, errors.Annotatef(err, "getting environ config")
   100  	}
   101  	env, err := environs.New(envCfg)
   102  	if err != nil {
   103  		return params.ErrorResults{}, errors.Annotatef(err, "getting environ")
   104  	}
   105  	for i, one := range metadata.Metadata {
   106  		md, err := api.parseMetadataListFromParams(one, env)
   107  		if err != nil {
   108  			all[i] = params.ErrorResult{Error: common.ServerError(err)}
   109  			continue
   110  		}
   111  		err = api.metadata.SaveMetadata(md)
   112  		all[i] = params.ErrorResult{Error: common.ServerError(err)}
   113  	}
   114  	return params.ErrorResults{Results: all}, nil
   115  }
   116  
   117  // Delete deletes cloud image metadata for given image ids.
   118  // It supports bulk calls.
   119  func (api *API) Delete(images params.MetadataImageIds) (params.ErrorResults, error) {
   120  	all := make([]params.ErrorResult, len(images.Ids))
   121  	for i, imageId := range images.Ids {
   122  		err := api.metadata.DeleteMetadata(imageId)
   123  		all[i] = params.ErrorResult{common.ServerError(err)}
   124  	}
   125  	return params.ErrorResults{Results: all}, nil
   126  }
   127  
   128  func parseMetadataToParams(p cloudimagemetadata.Metadata) params.CloudImageMetadata {
   129  	result := params.CloudImageMetadata{
   130  		ImageId:         p.ImageId,
   131  		Stream:          p.Stream,
   132  		Region:          p.Region,
   133  		Version:         p.Version,
   134  		Series:          p.Series,
   135  		Arch:            p.Arch,
   136  		VirtType:        p.VirtType,
   137  		RootStorageType: p.RootStorageType,
   138  		RootStorageSize: p.RootStorageSize,
   139  		Source:          p.Source,
   140  		Priority:        p.Priority,
   141  	}
   142  	return result
   143  }
   144  
   145  func (api *API) parseMetadataListFromParams(
   146  	p params.CloudImageMetadataList, env environs.Environ,
   147  ) ([]cloudimagemetadata.Metadata, error) {
   148  	results := make([]cloudimagemetadata.Metadata, len(p.Metadata))
   149  	for i, metadata := range p.Metadata {
   150  		result, err := api.parseMetadataFromParams(metadata, env)
   151  		if err != nil {
   152  			return nil, errors.Trace(err)
   153  		}
   154  		results[i] = result
   155  	}
   156  	return results, nil
   157  }
   158  
   159  func (api *API) parseMetadataFromParams(p params.CloudImageMetadata, env environs.Environ) (cloudimagemetadata.Metadata, error) {
   160  	result := cloudimagemetadata.Metadata{
   161  		cloudimagemetadata.MetadataAttributes{
   162  			Stream:          p.Stream,
   163  			Region:          p.Region,
   164  			Version:         p.Version,
   165  			Series:          p.Series,
   166  			Arch:            p.Arch,
   167  			VirtType:        p.VirtType,
   168  			RootStorageType: p.RootStorageType,
   169  			RootStorageSize: p.RootStorageSize,
   170  			Source:          p.Source,
   171  		},
   172  		p.Priority,
   173  		p.ImageId,
   174  	}
   175  
   176  	// Fill in any required default values.
   177  	if p.Stream == "" {
   178  		result.Stream = env.Config().ImageStream()
   179  	}
   180  	if p.Source == "" {
   181  		result.Source = "custom"
   182  	}
   183  	if result.Arch == "" {
   184  		result.Arch = "amd64"
   185  	}
   186  	if result.Series == "" {
   187  		result.Series = config.PreferredSeries(env.Config())
   188  	}
   189  	if result.Region == "" {
   190  		// If the env supports regions, use the env default.
   191  		if r, ok := env.(simplestreams.HasRegion); ok {
   192  			spec, err := r.Region()
   193  			if err != nil {
   194  				return cloudimagemetadata.Metadata{}, errors.Annotatef(err, "getting cloud region")
   195  			}
   196  			result.Region = spec.Region
   197  		}
   198  	}
   199  	return result, nil
   200  }
   201  
   202  // UpdateFromPublishedImages retrieves currently published image metadata and
   203  // updates stored ones accordingly.
   204  func (api *API) UpdateFromPublishedImages() error {
   205  	return api.retrievePublished()
   206  }
   207  
   208  func (api *API) retrievePublished() error {
   209  	envCfg, err := api.metadata.ModelConfig()
   210  	if err != nil {
   211  		return errors.Annotatef(err, "getting environ config")
   212  	}
   213  	env, err := environs.New(envCfg)
   214  	if err != nil {
   215  		return errors.Annotatef(err, "getting environ")
   216  	}
   217  
   218  	sources, err := environs.ImageMetadataSources(env)
   219  	if err != nil {
   220  		return errors.Annotatef(err, "getting cloud specific image metadata sources")
   221  	}
   222  
   223  	cons := envmetadata.NewImageConstraint(simplestreams.LookupParams{})
   224  	if inst, ok := env.(simplestreams.HasRegion); !ok {
   225  		// Published image metadata for some providers are in simple streams.
   226  		// Providers that do not rely on simplestreams, don't need to do anything here.
   227  		return nil
   228  	} else {
   229  		// If we can determine current region,
   230  		// we want only metadata specific to this region.
   231  		cloud, err := inst.Region()
   232  		if err != nil {
   233  			return errors.Annotatef(err, "getting cloud specific region information")
   234  		}
   235  		cons.CloudSpec = cloud
   236  	}
   237  
   238  	// We want all relevant metadata from all data sources.
   239  	for _, source := range sources {
   240  		logger.Debugf("looking in data source %v", source.Description())
   241  		metadata, info, err := envmetadata.Fetch([]simplestreams.DataSource{source}, cons)
   242  		if err != nil {
   243  			// Do not stop looking in other data sources if there is an issue here.
   244  			logger.Errorf("encountered %v while getting published images metadata from %v", err, source.Description())
   245  			continue
   246  		}
   247  		err = api.saveAll(info, source.Priority(), metadata)
   248  		if err != nil {
   249  			// Do not stop looking in other data sources if there is an issue here.
   250  			logger.Errorf("encountered %v while saving published images metadata from %v", err, source.Description())
   251  		}
   252  	}
   253  	return nil
   254  }
   255  
   256  func (api *API) saveAll(info *simplestreams.ResolveInfo, priority int, published []*envmetadata.ImageMetadata) error {
   257  	metadata, parseErrs := convertToParams(info, priority, published)
   258  
   259  	// Store converted metadata.
   260  	// Note that whether the metadata actually needs
   261  	// to be stored will be determined within this call.
   262  	errs, err := api.Save(metadata)
   263  	if err != nil {
   264  		return errors.Annotatef(err, "saving published images metadata")
   265  	}
   266  
   267  	return processErrors(append(errs.Results, parseErrs...))
   268  }
   269  
   270  // convertToParams converts model-specific images metadata to structured metadata format.
   271  var convertToParams = func(info *simplestreams.ResolveInfo, priority int, published []*envmetadata.ImageMetadata) (params.MetadataSaveParams, []params.ErrorResult) {
   272  	metadata := []params.CloudImageMetadataList{{}}
   273  	errs := []params.ErrorResult{}
   274  	for _, p := range published {
   275  		s, err := series.VersionSeries(p.Version)
   276  		if err != nil {
   277  			errs = append(errs, params.ErrorResult{Error: common.ServerError(err)})
   278  			continue
   279  		}
   280  
   281  		m := params.CloudImageMetadata{
   282  			Source:          info.Source,
   283  			ImageId:         p.Id,
   284  			Stream:          p.Stream,
   285  			Region:          p.RegionName,
   286  			Arch:            p.Arch,
   287  			VirtType:        p.VirtType,
   288  			RootStorageType: p.Storage,
   289  			Series:          s,
   290  			Priority:        priority,
   291  		}
   292  
   293  		metadata[0].Metadata = append(metadata[0].Metadata, m)
   294  	}
   295  	return params.MetadataSaveParams{Metadata: metadata}, errs
   296  }
   297  
   298  func processErrors(errs []params.ErrorResult) error {
   299  	msgs := []string{}
   300  	for _, e := range errs {
   301  		if e.Error != nil && e.Error.Message != "" {
   302  			msgs = append(msgs, e.Error.Message)
   303  		}
   304  	}
   305  	if len(msgs) != 0 {
   306  		return errors.Errorf("saving some image metadata:\n%v", strings.Join(msgs, "\n"))
   307  	}
   308  	return nil
   309  }
   310  
   311  // metadataList is a convenience type enabling to sort
   312  // a collection of Metadata in order of priority.
   313  type metadataList []params.CloudImageMetadata
   314  
   315  // Len implements sort.Interface
   316  func (m metadataList) Len() int {
   317  	return len(m)
   318  }
   319  
   320  // Less implements sort.Interface and sorts image metadata by priority.
   321  func (m metadataList) Less(i, j int) bool {
   322  	return m[i].Priority < m[j].Priority
   323  }
   324  
   325  // Swap implements sort.Interface
   326  func (m metadataList) Swap(i, j int) {
   327  	m[i], m[j] = m[j], m[i]
   328  }