github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/state/toolstorage/tools.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package toolstorage 5 6 import ( 7 "fmt" 8 "io" 9 10 "github.com/juju/blobstore" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 jujutxn "github.com/juju/txn" 14 "gopkg.in/mgo.v2" 15 "gopkg.in/mgo.v2/bson" 16 "gopkg.in/mgo.v2/txn" 17 18 "github.com/juju/juju/version" 19 ) 20 21 var logger = loggo.GetLogger("juju.state.toolstorage") 22 23 type toolsStorage struct { 24 envUUID string 25 managedStorage blobstore.ManagedStorage 26 metadataCollection *mgo.Collection 27 txnRunner jujutxn.Runner 28 } 29 30 var _ Storage = (*toolsStorage)(nil) 31 32 // NewStorage constructs a new Storage that stores tools tarballs 33 // in the provided ManagedStorage, and tools metadata in the provided 34 // collection using the provided transaction runner. 35 func NewStorage( 36 envUUID string, 37 managedStorage blobstore.ManagedStorage, 38 metadataCollection *mgo.Collection, 39 runner jujutxn.Runner, 40 ) Storage { 41 return &toolsStorage{ 42 envUUID: envUUID, 43 managedStorage: managedStorage, 44 metadataCollection: metadataCollection, 45 txnRunner: runner, 46 } 47 } 48 49 func (s *toolsStorage) AddTools(r io.Reader, metadata Metadata) (resultErr error) { 50 // Add the tools tarball to storage. 51 path := toolsPath(metadata.Version, metadata.SHA256) 52 if err := s.managedStorage.PutForEnvironment(s.envUUID, path, r, metadata.Size); err != nil { 53 return errors.Annotate(err, "cannot store tools tarball") 54 } 55 defer func() { 56 if resultErr == nil { 57 return 58 } 59 err := s.managedStorage.RemoveForEnvironment(s.envUUID, path) 60 if err != nil { 61 logger.Errorf("failed to remove tools blob: %v", err) 62 } 63 }() 64 65 newDoc := toolsMetadataDoc{ 66 Id: metadata.Version.String(), 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 74 // existing path so we 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 tools. 83 // Subsequent attempts to add tools 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.toolsMetadata(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 tools metadata") 111 } 112 113 if oldPath != "" && oldPath != path { 114 // Attempt to remove the old path. Failure is non-fatal. 115 err := s.managedStorage.RemoveForEnvironment(s.envUUID, oldPath) 116 if err != nil { 117 logger.Errorf("failed to remove old tools blob: %v", err) 118 } else { 119 logger.Debugf("removed old tools blob") 120 } 121 } 122 return nil 123 } 124 125 func (s *toolsStorage) Tools(v version.Binary) (Metadata, io.ReadCloser, error) { 126 metadataDoc, err := s.toolsMetadata(v) 127 if err != nil { 128 return Metadata{}, nil, err 129 } 130 tools, err := s.toolsTarball(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, tools, nil 140 } 141 142 func (s *toolsStorage) Metadata(v version.Binary) (Metadata, error) { 143 metadataDoc, err := s.toolsMetadata(v) 144 if err != nil { 145 return Metadata{}, err 146 } 147 metadata := Metadata{ 148 Version: metadataDoc.Version, 149 Size: metadataDoc.Size, 150 SHA256: metadataDoc.SHA256, 151 } 152 return metadata, nil 153 } 154 155 func (s *toolsStorage) AllMetadata() ([]Metadata, error) { 156 var docs []toolsMetadataDoc 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 metadata := Metadata{ 163 Version: doc.Version, 164 Size: doc.Size, 165 SHA256: doc.SHA256, 166 } 167 list[i] = metadata 168 } 169 return list, nil 170 } 171 172 type toolsMetadataDoc struct { 173 Id string `bson:"_id"` 174 Version version.Binary `bson:"version"` 175 Size int64 `bson:"size"` 176 SHA256 string `bson:"sha256,omitempty"` 177 Path string `bson:"path"` 178 } 179 180 func (s *toolsStorage) toolsMetadata(v version.Binary) (toolsMetadataDoc, error) { 181 var doc toolsMetadataDoc 182 err := s.metadataCollection.Find(bson.D{{"_id", v.String()}}).One(&doc) 183 if err == mgo.ErrNotFound { 184 return doc, errors.NotFoundf("%v tools metadata", v) 185 } else if err != nil { 186 return doc, err 187 } 188 return doc, nil 189 } 190 191 func (s *toolsStorage) toolsTarball(path string) (io.ReadCloser, error) { 192 r, _, err := s.managedStorage.GetForEnvironment(s.envUUID, path) 193 return r, err 194 } 195 196 // toolsPath returns the storage path for the specified tools. 197 func toolsPath(v version.Binary, hash string) string { 198 return fmt.Sprintf("tools/%s-%s", v, hash) 199 }