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 }