~juju-qa/ubuntu/trusty/juju/juju-1.25.8

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/state/imagestorage/image.go

  • Committer: Nicholas Skaggs
  • Date: 2016-12-02 18:01:10 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161202180110-dl1helep8qfebmhx
ImportĀ upstreamĀ 1.25.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}