github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/binarystorage/binarystorage.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package binarystorage 5 6 import ( 7 "fmt" 8 "io" 9 10 "github.com/juju/errors" 11 "github.com/juju/juju/mongo" 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 20 var logger = loggo.GetLogger("juju.state.binarystorage") 21 22 type binaryStorage struct { 23 modelUUID string 24 managedStorage blobstore.ManagedStorage 25 metadataCollection mongo.Collection 26 txnRunner jujutxn.Runner 27 } 28 29 var _ Storage = (*binaryStorage)(nil) 30 31 // New constructs a new Storage that stores binary files in the provided 32 // ManagedStorage, and metadata in the provided collection using the provided 33 // transaction runner. 34 func New( 35 modelUUID string, 36 managedStorage blobstore.ManagedStorage, 37 metadataCollection mongo.Collection, 38 runner jujutxn.Runner, 39 ) Storage { 40 return &binaryStorage{ 41 modelUUID: modelUUID, 42 managedStorage: managedStorage, 43 metadataCollection: metadataCollection, 44 txnRunner: runner, 45 } 46 } 47 48 // Add implements Storage.Add. 49 func (s *binaryStorage) Add(r io.Reader, metadata Metadata) (resultErr error) { 50 // Add the binary file to storage. 51 path := fmt.Sprintf("tools/%s-%s", metadata.Version, metadata.SHA256) 52 if err := s.managedStorage.PutForBucket(s.modelUUID, path, r, metadata.Size); err != nil { 53 return errors.Annotate(err, "cannot store binary file") 54 } 55 defer func() { 56 if resultErr == nil { 57 return 58 } 59 err := s.managedStorage.RemoveForBucket(s.modelUUID, path) 60 if err != nil { 61 logger.Errorf("failed to remove binary blob: %v", err) 62 } 63 }() 64 65 newDoc := metadataDoc{ 66 Id: metadata.Version, 67 Version: metadata.Version, 68 Size: metadata.Size, 69 SHA256: metadata.SHA256, 70 Path: path, 71 } 72 73 // Add or replace metadata. If replacing, record the existing path so we 74 // can remove it later. 75 var oldPath string 76 buildTxn := func(attempt int) ([]txn.Op, error) { 77 op := txn.Op{ 78 C: s.metadataCollection.Name(), 79 Id: newDoc.Id, 80 } 81 82 // On the first attempt we assume we're adding new binary files. 83 // Subsequent attempts to add files will fetch the existing 84 // doc, record the old path, and attempt to update the 85 // size, path and hash fields. 86 if attempt == 0 { 87 op.Assert = txn.DocMissing 88 op.Insert = &newDoc 89 } else { 90 oldDoc, err := s.findMetadata(metadata.Version) 91 if err != nil { 92 return nil, err 93 } 94 oldPath = oldDoc.Path 95 op.Assert = bson.D{{"path", oldPath}} 96 if oldPath != path { 97 op.Update = bson.D{{ 98 "$set", bson.D{ 99 {"size", metadata.Size}, 100 {"sha256", metadata.SHA256}, 101 {"path", path}, 102 }, 103 }} 104 } 105 } 106 return []txn.Op{op}, nil 107 } 108 err := s.txnRunner.Run(buildTxn) 109 if err != nil { 110 return errors.Annotate(err, "cannot store binary metadata") 111 } 112 113 if oldPath != "" && oldPath != path { 114 // Attempt to remove the old path. Failure is non-fatal. 115 err := s.managedStorage.RemoveForBucket(s.modelUUID, oldPath) 116 if err != nil { 117 logger.Errorf("failed to remove old binary blob: %v", err) 118 } else { 119 logger.Debugf("removed old binary blob") 120 } 121 } 122 return nil 123 } 124 125 func (s *binaryStorage) Open(version string) (Metadata, io.ReadCloser, error) { 126 metadataDoc, err := s.findMetadata(version) 127 if err != nil { 128 return Metadata{}, nil, err 129 } 130 r, _, err := s.managedStorage.GetForBucket(s.modelUUID, metadataDoc.Path) 131 if err != nil { 132 return Metadata{}, nil, err 133 } 134 metadata := Metadata{ 135 Version: metadataDoc.Version, 136 Size: metadataDoc.Size, 137 SHA256: metadataDoc.SHA256, 138 } 139 return metadata, r, nil 140 } 141 142 func (s *binaryStorage) Metadata(version string) (Metadata, error) { 143 metadataDoc, err := s.findMetadata(version) 144 if err != nil { 145 return Metadata{}, err 146 } 147 return Metadata{ 148 Version: metadataDoc.Version, 149 Size: metadataDoc.Size, 150 SHA256: metadataDoc.SHA256, 151 }, nil 152 } 153 154 func (s *binaryStorage) AllMetadata() ([]Metadata, error) { 155 var docs []metadataDoc 156 if err := s.metadataCollection.Find(nil).All(&docs); err != nil { 157 return nil, err 158 } 159 list := make([]Metadata, len(docs)) 160 for i, doc := range docs { 161 list[i] = Metadata{ 162 Version: doc.Version, 163 Size: doc.Size, 164 SHA256: doc.SHA256, 165 } 166 } 167 return list, nil 168 } 169 170 type metadataDoc struct { 171 Id string `bson:"_id"` 172 Version string `bson:"version"` 173 Size int64 `bson:"size"` 174 SHA256 string `bson:"sha256,omitempty"` 175 Path string `bson:"path"` 176 } 177 178 func (s *binaryStorage) findMetadata(version string) (metadataDoc, error) { 179 var doc metadataDoc 180 err := s.metadataCollection.FindId(version).One(&doc) 181 if err == mgo.ErrNotFound { 182 return doc, errors.NotFoundf("%v binary metadata", version) 183 } 184 return doc, err 185 }