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 }