github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/imagestorage/image.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package imagestorage 5 6 import ( 7 "fmt" 8 "io" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 jujutxn "github.com/juju/txn" 14 "gopkg.in/juju/blobstore.v2" 15 "gopkg.in/mgo.v2" 16 "gopkg.in/mgo.v2/bson" 17 "gopkg.in/mgo.v2/txn" 18 19 "github.com/juju/juju/mongo" 20 ) 21 22 var logger = loggo.GetLogger("juju.state.imagestorage") 23 24 const ( 25 // imagemetadataC is the collection used to store image metadata. 26 imagemetadataC = "imagemetadata" 27 28 // ImagesDB is the database used to store image blobs. 29 ImagesDB = "osimages" 30 ) 31 32 type imageStorage struct { 33 modelUUID string 34 metadataCollection *mgo.Collection 35 blobDb *mgo.Database 36 } 37 38 var _ Storage = (*imageStorage)(nil) 39 40 // NewStorage constructs a new Storage that stores image blobs 41 // in an "osimages" database. Image metadata is also stored in this 42 // database in the "imagemetadata" collection. 43 func NewStorage( 44 session *mgo.Session, 45 modelUUID string, 46 ) Storage { 47 blobDb := session.DB(ImagesDB) 48 metadataCollection := blobDb.C(imagemetadataC) 49 return &imageStorage{ 50 modelUUID, 51 metadataCollection, 52 blobDb, 53 } 54 } 55 56 // Override for testing. 57 var getManagedStorage = func(session *mgo.Session) blobstore.ManagedStorage { 58 rs := blobstore.NewGridFS(ImagesDB, ImagesDB, session) 59 db := session.DB(ImagesDB) 60 metadataDb := db.With(session) 61 return blobstore.NewManagedStorage(metadataDb, rs) 62 } 63 64 func (s *imageStorage) getManagedStorage(session *mgo.Session) blobstore.ManagedStorage { 65 return getManagedStorage(session) 66 } 67 68 func (s *imageStorage) txnRunner(session *mgo.Session) jujutxn.Runner { 69 db := s.metadataCollection.Database 70 runnerDb := db.With(session) 71 return txnRunner(runnerDb) 72 } 73 74 // Override for testing. 75 var txnRunner = func(db *mgo.Database) jujutxn.Runner { 76 return jujutxn.NewRunner(jujutxn.RunnerParams{Database: db}) 77 } 78 79 // AddImage is defined on the Storage interface. 80 func (s *imageStorage) AddImage(r io.Reader, metadata *Metadata) (resultErr error) { 81 session := s.blobDb.Session.Copy() 82 defer session.Close() 83 managedStorage := s.getManagedStorage(session) 84 path := imagePath(metadata.Kind, metadata.Series, metadata.Arch, metadata.SHA256) 85 if err := managedStorage.PutForBucket(s.modelUUID, path, r, metadata.Size); err != nil { 86 return errors.Annotate(err, "cannot store image") 87 } 88 defer func() { 89 if resultErr == nil { 90 return 91 } 92 err := managedStorage.RemoveForBucket(s.modelUUID, path) 93 if err != nil { 94 logger.Errorf("failed to remove image blob: %v", err) 95 } 96 }() 97 98 newDoc := imageMetadataDoc{ 99 Id: docId(metadata), 100 ModelUUID: s.modelUUID, 101 Kind: metadata.Kind, 102 Series: metadata.Series, 103 Arch: metadata.Arch, 104 Size: metadata.Size, 105 SHA256: metadata.SHA256, 106 SourceURL: metadata.SourceURL, 107 Path: path, 108 // TODO(fwereade): 2016-03-17 lp:1558657 109 Created: time.Now(), 110 } 111 112 // Add or replace metadata. If replacing, record the 113 // existing path so we can remove the blob later. 114 var oldPath string 115 buildTxn := func(attempt int) ([]txn.Op, error) { 116 op := txn.Op{ 117 C: imagemetadataC, 118 Id: newDoc.Id, 119 } 120 121 // On the first attempt we assume we're adding a new image blob. 122 // Subsequent attempts to add image will fetch the existing 123 // doc, record the old path, and attempt to update the 124 // size, path and hash fields. 125 if attempt == 0 { 126 op.Assert = txn.DocMissing 127 op.Insert = &newDoc 128 } else { 129 oldDoc, err := s.imageMetadataDoc(metadata.ModelUUID, metadata.Kind, metadata.Series, metadata.Arch) 130 if err != nil { 131 return nil, err 132 } 133 oldPath = oldDoc.Path 134 op.Assert = bson.D{{"path", oldPath}} 135 if oldPath != path { 136 op.Update = bson.D{{ 137 "$set", bson.D{ 138 {"size", metadata.Size}, 139 {"sha256", metadata.SHA256}, 140 {"path", path}, 141 }, 142 }} 143 } 144 } 145 return []txn.Op{op}, nil 146 } 147 txnRunner := s.txnRunner(session) 148 err := txnRunner.Run(buildTxn) 149 if err != nil { 150 return errors.Annotate(err, "cannot store image metadata") 151 } 152 153 if oldPath != "" && oldPath != path { 154 // Attempt to remove the old path. Failure is non-fatal. 155 err := managedStorage.RemoveForBucket(s.modelUUID, oldPath) 156 if err != nil { 157 logger.Errorf("failed to remove old image blob: %v", err) 158 } else { 159 logger.Debugf("removed old image blob") 160 } 161 } 162 return nil 163 } 164 165 // ListImages is defined on the Storage interface. 166 func (s *imageStorage) ListImages(filter ImageFilter) ([]*Metadata, error) { 167 metadataDocs, err := s.listImageMetadataDocs(s.modelUUID, filter.Kind, filter.Series, filter.Arch) 168 if err != nil { 169 return nil, errors.Annotate(err, "cannot list image metadata") 170 } 171 result := make([]*Metadata, len(metadataDocs)) 172 for i, metadataDoc := range metadataDocs { 173 result[i] = &Metadata{ 174 ModelUUID: s.modelUUID, 175 Kind: metadataDoc.Kind, 176 Series: metadataDoc.Series, 177 Arch: metadataDoc.Arch, 178 Size: metadataDoc.Size, 179 SHA256: metadataDoc.SHA256, 180 Created: metadataDoc.Created, 181 SourceURL: metadataDoc.SourceURL, 182 } 183 } 184 return result, nil 185 } 186 187 // DeleteImage is defined on the Storage interface. 188 func (s *imageStorage) DeleteImage(metadata *Metadata) (resultErr error) { 189 session := s.blobDb.Session.Copy() 190 defer session.Close() 191 managedStorage := s.getManagedStorage(session) 192 path := imagePath(metadata.Kind, metadata.Series, metadata.Arch, metadata.SHA256) 193 if err := managedStorage.RemoveForBucket(s.modelUUID, path); err != nil { 194 return errors.Annotate(err, "cannot remove image blob") 195 } 196 // Remove the metadata. 197 buildTxn := func(attempt int) ([]txn.Op, error) { 198 op := txn.Op{ 199 C: imagemetadataC, 200 Id: docId(metadata), 201 Remove: true, 202 } 203 return []txn.Op{op}, nil 204 } 205 txnRunner := s.txnRunner(session) 206 err := txnRunner.Run(buildTxn) 207 // Metadata already removed, we don't care. 208 if err == mgo.ErrNotFound { 209 return nil 210 } 211 return errors.Annotate(err, "cannot remove image metadata") 212 } 213 214 // imageCloser encapsulates an image reader and session 215 // so that both are closed together. 216 type imageCloser struct { 217 io.ReadCloser 218 session *mgo.Session 219 } 220 221 func (c *imageCloser) Close() error { 222 c.session.Close() 223 return c.ReadCloser.Close() 224 } 225 226 // Image is defined on the Storage interface. 227 func (s *imageStorage) Image(kind, series, arch string) (*Metadata, io.ReadCloser, error) { 228 metadataDoc, err := s.imageMetadataDoc(s.modelUUID, kind, series, arch) 229 if err != nil { 230 return nil, nil, err 231 } 232 session := s.blobDb.Session.Copy() 233 managedStorage := s.getManagedStorage(session) 234 image, err := s.imageBlob(managedStorage, metadataDoc.Path) 235 if err != nil { 236 return nil, nil, err 237 } 238 metadata := &Metadata{ 239 ModelUUID: s.modelUUID, 240 Kind: metadataDoc.Kind, 241 Series: metadataDoc.Series, 242 Arch: metadataDoc.Arch, 243 Size: metadataDoc.Size, 244 SHA256: metadataDoc.SHA256, 245 SourceURL: metadataDoc.SourceURL, 246 Created: metadataDoc.Created, 247 } 248 imageResult := &imageCloser{ 249 image, 250 session, 251 } 252 return metadata, imageResult, nil 253 } 254 255 type imageMetadataDoc struct { 256 Id string `bson:"_id"` 257 ModelUUID string `bson:"modelUUID"` 258 Kind string `bson:"kind"` 259 Series string `bson:"series"` 260 Arch string `bson:"arch"` 261 Size int64 `bson:"size"` 262 SHA256 string `bson:"sha256"` 263 Path string `bson:"path"` 264 Created time.Time `bson:"created"` 265 SourceURL string `bson:"sourceurl"` 266 } 267 268 func (s *imageStorage) imageMetadataDoc(modelUUID, kind, series, arch string) (imageMetadataDoc, error) { 269 var doc imageMetadataDoc 270 id := fmt.Sprintf("%s-%s-%s-%s", modelUUID, kind, series, arch) 271 coll, closer := mongo.CollectionFromName(s.metadataCollection.Database, imagemetadataC) 272 defer closer() 273 err := coll.FindId(id).One(&doc) 274 if err == mgo.ErrNotFound { 275 return doc, errors.NotFoundf("%v image metadata", id) 276 } else if err != nil { 277 return doc, err 278 } 279 return doc, nil 280 } 281 282 func (s *imageStorage) listImageMetadataDocs(modelUUID, kind, series, arch string) ([]imageMetadataDoc, error) { 283 coll, closer := mongo.CollectionFromName(s.metadataCollection.Database, imagemetadataC) 284 defer closer() 285 imageDocs := []imageMetadataDoc{} 286 filter := bson.D{{"modelUUID", modelUUID}} 287 if kind != "" { 288 filter = append(filter, bson.DocElem{"kind", kind}) 289 } 290 if series != "" { 291 filter = append(filter, bson.DocElem{"series", series}) 292 } 293 if arch != "" { 294 filter = append(filter, bson.DocElem{"arch", arch}) 295 } 296 err := coll.Find(filter).All(&imageDocs) 297 return imageDocs, err 298 } 299 300 func (s *imageStorage) imageBlob(managedStorage blobstore.ManagedStorage, path string) (io.ReadCloser, error) { 301 r, _, err := managedStorage.GetForBucket(s.modelUUID, path) 302 return r, err 303 } 304 305 // imagePath returns the storage path for the specified image. 306 func imagePath(kind, series, arch, checksum string) string { 307 return fmt.Sprintf("images/%s-%s-%s:%s", kind, series, arch, checksum) 308 } 309 310 // docId returns an id for the mongo image metadata document. 311 func docId(metadata *Metadata) string { 312 return fmt.Sprintf("%s-%s-%s-%s", metadata.ModelUUID, metadata.Kind, metadata.Series, metadata.Arch) 313 }