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