github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/state/cloudimagemetadata/image.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cloudimagemetadata 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 jujutxn "github.com/juju/txn" 13 "gopkg.in/mgo.v2" 14 "gopkg.in/mgo.v2/bson" 15 "gopkg.in/mgo.v2/txn" 16 ) 17 18 var logger = loggo.GetLogger("juju.state.cloudimagemetadata") 19 20 type storage struct { 21 envuuid string 22 collection string 23 store DataStore 24 } 25 26 var _ Storage = (*storage)(nil) 27 28 // NewStorage constructs a new Storage that stores image metadata 29 // in the provided data store. 30 func NewStorage(envuuid, collectionName string, store DataStore) Storage { 31 return &storage{envuuid, collectionName, store} 32 } 33 34 var emptyMetadata = Metadata{} 35 36 // SaveMetadata implements Storage.SaveMetadata and behaves as save-or-update. 37 func (s *storage) SaveMetadata(metadata Metadata) error { 38 newDoc := s.mongoDoc(metadata) 39 40 buildTxn := func(attempt int) ([]txn.Op, error) { 41 op := txn.Op{ 42 C: s.collection, 43 Id: newDoc.Id, 44 } 45 46 // Check if this image metadata is already known. 47 existing, err := s.getMetadata(newDoc.Id) 48 if err != nil { 49 return nil, errors.Trace(err) 50 } 51 if existing.MetadataAttributes == metadata.MetadataAttributes { 52 // may need to updated imageId 53 if existing.ImageId != metadata.ImageId { 54 op.Assert = txn.DocExists 55 op.Update = bson.D{{"$set", bson.D{{"image_id", metadata.ImageId}}}} 56 logger.Debugf("updating cloud image id for metadata %v", newDoc.Id) 57 } else { 58 return nil, jujutxn.ErrNoOperations 59 } 60 } else { 61 op.Assert = txn.DocMissing 62 op.Insert = &newDoc 63 logger.Debugf("inserting cloud image metadata for %v", newDoc.Id) 64 } 65 return []txn.Op{op}, nil 66 } 67 68 err := s.store.RunTransaction(buildTxn) 69 if err != nil { 70 return errors.Annotatef(err, "cannot save metadata for cloud image %v", newDoc.ImageId) 71 } 72 return nil 73 } 74 75 func (s *storage) getMetadata(id string) (Metadata, error) { 76 coll, closer := s.store.GetCollection(s.collection) 77 defer closer() 78 79 var old imagesMetadataDoc 80 err := coll.Find(bson.D{{"_id", id}}).One(&old) 81 if err != nil { 82 if err == mgo.ErrNotFound { 83 return emptyMetadata, nil 84 } 85 return emptyMetadata, errors.Trace(err) 86 } 87 return old.metadata(), nil 88 } 89 90 // imagesMetadataDoc results in immutable records. Updates are effectively 91 // a delate and an insert. 92 type imagesMetadataDoc struct { 93 // EnvUUID is the environment identifier. 94 EnvUUID string `bson:"env-uuid"` 95 96 // Id contains unique key for cloud image metadata. 97 // This is an amalgamation of all deterministic metadata attributes to ensure 98 // that there can be a public and custom image for the same attributes set. 99 Id string `bson:"_id"` 100 101 // ImageId is an image identifier. 102 ImageId string `bson:"image_id"` 103 104 // Stream contains reference to a particular stream, 105 // for e.g. "daily" or "released" 106 Stream string `bson:"stream"` 107 108 // Region is the name of cloud region associated with the image. 109 Region string `bson:"region"` 110 111 // Series is Os version, for e.g. "quantal". 112 Series string `bson:"series"` 113 114 // Arch is the architecture for this cloud image, for e.g. "amd64" 115 Arch string `bson:"arch"` 116 117 // VirtType contains virtualisation type of the cloud image, for e.g. "pv", "hvm". "kvm". 118 VirtType string `bson:"virt_type,omitempty"` 119 120 // RootStorageType contains type of root storage, for e.g. "ebs", "instance". 121 RootStorageType string `bson:"root_storage_type,omitempty"` 122 123 // RootStorageSize contains size of root storage in gigabytes (GB). 124 RootStorageSize uint64 `bson:"root_storage_size"` 125 126 // DateCreated is the date/time when this doc was created. 127 DateCreated int64 `bson:"date_created"` 128 129 // Source describes where this image is coming from: is it public? custom? 130 Source SourceType `bson:"source"` 131 } 132 133 // SourceType values define source type. 134 type SourceType string 135 136 const ( 137 // Public type identifies image as public. 138 Public SourceType = "public" 139 140 // Custom type identifies image as custom. 141 Custom SourceType = "custom" 142 ) 143 144 func (m imagesMetadataDoc) metadata() Metadata { 145 r := Metadata{ 146 MetadataAttributes{ 147 Source: m.Source, 148 Stream: m.Stream, 149 Region: m.Region, 150 Series: m.Series, 151 Arch: m.Arch, 152 RootStorageType: m.RootStorageType, 153 VirtType: m.VirtType, 154 }, 155 m.ImageId, 156 } 157 if m.RootStorageSize != 0 { 158 r.RootStorageSize = &m.RootStorageSize 159 } 160 return r 161 } 162 163 func (s *storage) mongoDoc(m Metadata) imagesMetadataDoc { 164 r := imagesMetadataDoc{ 165 EnvUUID: s.envuuid, 166 Id: buildKey(m), 167 Stream: m.Stream, 168 Region: m.Region, 169 Series: m.Series, 170 Arch: m.Arch, 171 VirtType: m.VirtType, 172 RootStorageType: m.RootStorageType, 173 ImageId: m.ImageId, 174 DateCreated: time.Now().UnixNano(), 175 Source: m.Source, 176 } 177 if m.RootStorageSize != nil { 178 r.RootStorageSize = *m.RootStorageSize 179 } 180 return r 181 } 182 183 func buildKey(m Metadata) string { 184 return fmt.Sprintf("%s:%s:%s:%s:%s:%s:%s", 185 m.Stream, 186 m.Region, 187 m.Series, 188 m.Arch, 189 m.VirtType, 190 m.RootStorageType, 191 m.Source) 192 } 193 194 // FindMetadata implements Storage.FindMetadata. 195 // Results are sorted by date created and grouped by source. 196 func (s *storage) FindMetadata(criteria MetadataFilter) (map[SourceType][]Metadata, error) { 197 coll, closer := s.store.GetCollection(s.collection) 198 defer closer() 199 200 searchCriteria := buildSearchClauses(criteria) 201 var docs []imagesMetadataDoc 202 if err := coll.Find(searchCriteria).Sort("date_created").All(&docs); err != nil { 203 return nil, errors.Trace(err) 204 } 205 if len(docs) == 0 { 206 return nil, errors.NotFoundf("matching cloud image metadata") 207 } 208 209 metadata := make(map[SourceType][]Metadata) 210 for _, doc := range docs { 211 one := doc.metadata() 212 metadata[one.Source] = append(metadata[one.Source], one) 213 } 214 return metadata, nil 215 } 216 217 func buildSearchClauses(criteria MetadataFilter) bson.D { 218 all := bson.D{} 219 220 if criteria.Stream != "" { 221 all = append(all, bson.DocElem{"stream", criteria.Stream}) 222 } 223 224 if criteria.Region != "" { 225 all = append(all, bson.DocElem{"region", criteria.Region}) 226 } 227 228 if len(criteria.Series) != 0 { 229 all = append(all, bson.DocElem{"series", bson.D{{"$in", criteria.Series}}}) 230 } 231 232 if len(criteria.Arches) != 0 { 233 all = append(all, bson.DocElem{"arch", bson.D{{"$in", criteria.Arches}}}) 234 } 235 236 if criteria.VirtType != "" { 237 all = append(all, bson.DocElem{"virt_type", criteria.VirtType}) 238 } 239 240 if criteria.RootStorageType != "" { 241 all = append(all, bson.DocElem{"root_storage_type", criteria.RootStorageType}) 242 } 243 244 if len(all.Map()) == 0 { 245 return nil 246 } 247 return all 248 } 249 250 // MetadataFilter contains all metadata attributes that alow to find a particular 251 // cloud image metadata. Since size and source are not discriminating attributes 252 // for cloud image metadata, they are not included in search criteria. 253 type MetadataFilter struct { 254 // Region stores metadata region. 255 Region string `json:"region,omitempty"` 256 257 // Series stores all desired series. 258 Series []string `json:"series,omitempty"` 259 260 // Arches stores all desired architectures. 261 Arches []string `json:"arches,omitempty"` 262 263 // Stream can be "" or "released" for the default "released" stream, 264 // or "daily" for daily images, or any other stream that the available 265 // simplestreams metadata supports. 266 Stream string `json:"stream,omitempty"` 267 268 // VirtType stores virtualisation type. 269 VirtType string `json:"virt_type,omitempty"` 270 271 // RootStorageType stores storage type. 272 RootStorageType string `json:"root-storage-type,omitempty"` 273 }