1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"github.com/juju/blobstore"
12
"github.com/juju/errors"
13
"github.com/juju/loggo"
14
jujutxn "github.com/juju/txn"
16
"gopkg.in/mgo.v2/bson"
19
"github.com/juju/juju/mongo"
22
var logger = loggo.GetLogger("juju.state.imagestorage")
25
// imagemetadataC is the collection used to store image metadata.
26
imagemetadataC = "imagemetadata"
28
// ImagesDB is the database used to store image blobs.
32
type imageStorage struct {
34
metadataCollection *mgo.Collection
38
var _ Storage = (*imageStorage)(nil)
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.
47
blobDb := session.DB(ImagesDB)
48
metadataCollection := blobDb.C(imagemetadataC)
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)
64
func (s *imageStorage) getManagedStorage(session *mgo.Session) blobstore.ManagedStorage {
65
return getManagedStorage(session)
68
func (s *imageStorage) txnRunner(session *mgo.Session) jujutxn.Runner {
69
db := s.metadataCollection.Database
70
runnerDb := db.With(session)
71
return txnRunner(runnerDb)
74
// Override for testing.
75
var txnRunner = func(db *mgo.Database) jujutxn.Runner {
76
return jujutxn.NewRunner(jujutxn.RunnerParams{Database: db})
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()
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")
92
err := managedStorage.RemoveForEnvironment(s.envUUID, path)
94
logger.Errorf("failed to remove image blob: %v", err)
98
newDoc := imageMetadataDoc{
102
Series: metadata.Series,
105
SHA256: metadata.SHA256,
106
SourceURL: metadata.SourceURL,
111
// Add or replace metadata. If replacing, record the
112
// existing path so we can remove the blob later.
114
buildTxn := func(attempt int) ([]txn.Op, error) {
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.
125
op.Assert = txn.DocMissing
128
oldDoc, err := s.imageMetadataDoc(metadata.EnvUUID, metadata.Kind, metadata.Series, metadata.Arch)
132
oldPath = oldDoc.Path
133
op.Assert = bson.D{{"path", oldPath}}
137
{"size", metadata.Size},
138
{"sha256", metadata.SHA256},
144
return []txn.Op{op}, nil
146
txnRunner := s.txnRunner(session)
147
err := txnRunner.Run(buildTxn)
149
return errors.Annotate(err, "cannot store image metadata")
152
if oldPath != "" && oldPath != path {
153
// Attempt to remove the old path. Failure is non-fatal.
154
err := managedStorage.RemoveForEnvironment(s.envUUID, oldPath)
156
logger.Errorf("failed to remove old image blob: %v", err)
158
logger.Debugf("removed old image blob")
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)
168
return nil, errors.Annotate(err, "cannot list image metadata")
170
result := make([]*Metadata, len(metadataDocs))
171
for i, metadataDoc := range metadataDocs {
172
result[i] = &Metadata{
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,
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")
195
// Remove the metadata.
196
buildTxn := func(attempt int) ([]txn.Op, error) {
202
return []txn.Op{op}, nil
204
txnRunner := s.txnRunner(session)
205
err := txnRunner.Run(buildTxn)
206
// Metadata already removed, we don't care.
207
if err == mgo.ErrNotFound {
210
return errors.Annotate(err, "cannot remove image metadata")
213
// imageCloser encapsulates an image reader and session
214
// so that both are closed together.
215
type imageCloser struct {
220
func (c *imageCloser) Close() error {
222
return c.ReadCloser.Close()
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)
231
session := s.blobDb.Session.Copy()
232
managedStorage := s.getManagedStorage(session)
233
image, err := s.imageBlob(managedStorage, metadataDoc.Path)
237
metadata := &Metadata{
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,
247
imageResult := &imageCloser{
251
return metadata, imageResult, nil
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"`
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)
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 {
281
func (s *imageStorage) listImageMetadataDocs(envUUID, kind, series, arch string) ([]imageMetadataDoc, error) {
282
coll, closer := mongo.CollectionFromName(s.metadataCollection.Database, imagemetadataC)
284
imageDocs := []imageMetadataDoc{}
285
filter := bson.D{{"envuuid", envUUID}}
287
filter = append(filter, bson.DocElem{"kind", kind})
290
filter = append(filter, bson.DocElem{"series", series})
293
filter = append(filter, bson.DocElem{"arch", arch})
295
err := coll.Find(filter).All(&imageDocs)
296
return imageDocs, err
299
func (s *imageStorage) imageBlob(managedStorage blobstore.ManagedStorage, path string) (io.ReadCloser, error) {
300
r, _, err := managedStorage.GetForEnvironment(s.envUUID, path)
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)
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)