github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/blobstore" 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 jujutxn "github.com/juju/txn" 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 envUUID 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 envUUID string, 46 ) Storage { 47 blobDb := session.DB(ImagesDB) 48 metadataCollection := blobDb.C(imagemetadataC) 49 return &imageStorage{ 50 envUUID, 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.PutForEnvironment(s.envUUID, 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.RemoveForEnvironment(s.envUUID, 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 EnvUUID: s.envUUID, 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 Created: time.Now(), 109 } 110 111 // Add or replace metadata. If replacing, record the 112 // existing path so we can remove the blob later. 113 var oldPath string 114 buildTxn := func(attempt int) ([]txn.Op, error) { 115 op := txn.Op{ 116 C: imagemetadataC, 117 Id: newDoc.Id, 118 } 119 120 // On the first attempt we assume we're adding a new image blob. 121 // Subsequent attempts to add image will fetch the existing 122 // doc, record the old path, and attempt to update the 123 // size, path and hash fields. 124 if attempt == 0 { 125 op.Assert = txn.DocMissing 126 op.Insert = &newDoc 127 } else { 128 oldDoc, err := s.imageMetadataDoc(metadata.EnvUUID, metadata.Kind, metadata.Series, metadata.Arch) 129 if err != nil { 130 return nil, err 131 } 132 oldPath = oldDoc.Path 133 op.Assert = bson.D{{"path", oldPath}} 134 if oldPath != path { 135 op.Update = bson.D{{ 136 "$set", bson.D{ 137 {"size", metadata.Size}, 138 {"sha256", metadata.SHA256}, 139 {"path", path}, 140 }, 141 }} 142 } 143 } 144 return []txn.Op{op}, nil 145 } 146 txnRunner := s.txnRunner(session) 147 err := txnRunner.Run(buildTxn) 148 if err != nil { 149 return errors.Annotate(err, "cannot store image metadata") 150 } 151 152 if oldPath != "" && oldPath != path { 153 // Attempt to remove the old path. Failure is non-fatal. 154 err := managedStorage.RemoveForEnvironment(s.envUUID, oldPath) 155 if err != nil { 156 logger.Errorf("failed to remove old image blob: %v", err) 157 } else { 158 logger.Debugf("removed old image blob") 159 } 160 } 161 return nil 162 } 163 164 // ListImages is defined on the Storage interface. 165 func (s *imageStorage) ListImages(filter ImageFilter) ([]*Metadata, error) { 166 metadataDocs, err := s.listImageMetadataDocs(s.envUUID, filter.Kind, filter.Series, filter.Arch) 167 if err != nil { 168 return nil, errors.Annotate(err, "cannot list image metadata") 169 } 170 result := make([]*Metadata, len(metadataDocs)) 171 for i, metadataDoc := range metadataDocs { 172 result[i] = &Metadata{ 173 EnvUUID: s.envUUID, 174 Kind: metadataDoc.Kind, 175 Series: metadataDoc.Series, 176 Arch: metadataDoc.Arch, 177 Size: metadataDoc.Size, 178 SHA256: metadataDoc.SHA256, 179 Created: metadataDoc.Created, 180 SourceURL: metadataDoc.SourceURL, 181 } 182 } 183 return result, nil 184 } 185 186 // DeleteImage is defined on the Storage interface. 187 func (s *imageStorage) DeleteImage(metadata *Metadata) (resultErr error) { 188 session := s.blobDb.Session.Copy() 189 defer session.Close() 190 managedStorage := s.getManagedStorage(session) 191 path := imagePath(metadata.Kind, metadata.Series, metadata.Arch, metadata.SHA256) 192 if err := managedStorage.RemoveForEnvironment(s.envUUID, path); err != nil { 193 return errors.Annotate(err, "cannot remove image blob") 194 } 195 // Remove the metadata. 196 buildTxn := func(attempt int) ([]txn.Op, error) { 197 op := txn.Op{ 198 C: imagemetadataC, 199 Id: docId(metadata), 200 Remove: true, 201 } 202 return []txn.Op{op}, nil 203 } 204 txnRunner := s.txnRunner(session) 205 err := txnRunner.Run(buildTxn) 206 // Metadata already removed, we don't care. 207 if err == mgo.ErrNotFound { 208 return nil 209 } 210 return errors.Annotate(err, "cannot remove image metadata") 211 } 212 213 // imageCloser encapsulates an image reader and session 214 // so that both are closed together. 215 type imageCloser struct { 216 io.ReadCloser 217 session *mgo.Session 218 } 219 220 func (c *imageCloser) Close() error { 221 c.session.Close() 222 return c.ReadCloser.Close() 223 } 224 225 // Image is defined on the Storage interface. 226 func (s *imageStorage) Image(kind, series, arch string) (*Metadata, io.ReadCloser, error) { 227 metadataDoc, err := s.imageMetadataDoc(s.envUUID, kind, series, arch) 228 if err != nil { 229 return nil, nil, err 230 } 231 session := s.blobDb.Session.Copy() 232 managedStorage := s.getManagedStorage(session) 233 image, err := s.imageBlob(managedStorage, metadataDoc.Path) 234 if err != nil { 235 return nil, nil, err 236 } 237 metadata := &Metadata{ 238 EnvUUID: s.envUUID, 239 Kind: metadataDoc.Kind, 240 Series: metadataDoc.Series, 241 Arch: metadataDoc.Arch, 242 Size: metadataDoc.Size, 243 SHA256: metadataDoc.SHA256, 244 SourceURL: metadataDoc.SourceURL, 245 Created: metadataDoc.Created, 246 } 247 imageResult := &imageCloser{ 248 image, 249 session, 250 } 251 return metadata, imageResult, nil 252 } 253 254 type imageMetadataDoc struct { 255 Id string `bson:"_id"` 256 EnvUUID string `bson:"envuuid"` 257 Kind string `bson:"kind"` 258 Series string `bson:"series"` 259 Arch string `bson:"arch"` 260 Size int64 `bson:"size"` 261 SHA256 string `bson:"sha256"` 262 Path string `bson:"path"` 263 Created time.Time `bson:"created"` 264 SourceURL string `bson:"sourceurl"` 265 } 266 267 func (s *imageStorage) imageMetadataDoc(envUUID, kind, series, arch string) (imageMetadataDoc, error) { 268 var doc imageMetadataDoc 269 id := fmt.Sprintf("%s-%s-%s-%s", envUUID, kind, series, arch) 270 coll, closer := mongo.CollectionFromName(s.metadataCollection.Database, imagemetadataC) 271 defer closer() 272 err := coll.FindId(id).One(&doc) 273 if err == mgo.ErrNotFound { 274 return doc, errors.NotFoundf("%v image metadata", id) 275 } else if err != nil { 276 return doc, err 277 } 278 return doc, nil 279 } 280 281 func (s *imageStorage) listImageMetadataDocs(envUUID, kind, series, arch string) ([]imageMetadataDoc, error) { 282 coll, closer := mongo.CollectionFromName(s.metadataCollection.Database, imagemetadataC) 283 defer closer() 284 imageDocs := []imageMetadataDoc{} 285 filter := bson.D{{"envuuid", envUUID}} 286 if kind != "" { 287 filter = append(filter, bson.DocElem{"kind", kind}) 288 } 289 if series != "" { 290 filter = append(filter, bson.DocElem{"series", series}) 291 } 292 if arch != "" { 293 filter = append(filter, bson.DocElem{"arch", arch}) 294 } 295 err := coll.Find(filter).All(&imageDocs) 296 return imageDocs, err 297 } 298 299 func (s *imageStorage) imageBlob(managedStorage blobstore.ManagedStorage, path string) (io.ReadCloser, error) { 300 r, _, err := managedStorage.GetForEnvironment(s.envUUID, path) 301 return r, err 302 } 303 304 // imagePath returns the storage path for the specified image. 305 func imagePath(kind, series, arch, checksum string) string { 306 return fmt.Sprintf("images/%s-%s-%s:%s", kind, series, arch, checksum) 307 } 308 309 // docId returns an id for the mongo image metadata document. 310 func docId(metadata *Metadata) string { 311 return fmt.Sprintf("%s-%s-%s-%s", metadata.EnvUUID, metadata.Kind, metadata.Series, metadata.Arch) 312 }