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