github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/state/toolstorage/tools_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package toolstorage_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "strings" 12 stdtesting "testing" 13 14 "github.com/juju/blobstore" 15 "github.com/juju/errors" 16 gitjujutesting "github.com/juju/testing" 17 jc "github.com/juju/testing/checkers" 18 jujutxn "github.com/juju/txn" 19 txntesting "github.com/juju/txn/testing" 20 gc "gopkg.in/check.v1" 21 "gopkg.in/mgo.v2" 22 23 "github.com/juju/juju/state/toolstorage" 24 "github.com/juju/juju/testing" 25 "github.com/juju/juju/version" 26 ) 27 28 var _ = gc.Suite(&ToolsSuite{}) 29 30 func TestPackage(t *stdtesting.T) { 31 gc.TestingT(t) 32 } 33 34 type ToolsSuite struct { 35 testing.BaseSuite 36 mongo *gitjujutesting.MgoInstance 37 session *mgo.Session 38 storage toolstorage.Storage 39 managedStorage blobstore.ManagedStorage 40 metadataCollection *mgo.Collection 41 txnRunner jujutxn.Runner 42 } 43 44 func (s *ToolsSuite) SetUpTest(c *gc.C) { 45 s.BaseSuite.SetUpTest(c) 46 s.mongo = &gitjujutesting.MgoInstance{} 47 s.mongo.Start(nil) 48 49 var err error 50 s.session, err = s.mongo.Dial() 51 c.Assert(err, jc.ErrorIsNil) 52 rs := blobstore.NewGridFS("blobstore", "my-uuid", s.session) 53 catalogue := s.session.DB("catalogue") 54 s.managedStorage = blobstore.NewManagedStorage(catalogue, rs) 55 s.metadataCollection = catalogue.C("toolsmetadata") 56 s.txnRunner = jujutxn.NewRunner(jujutxn.RunnerParams{Database: catalogue}) 57 s.storage = toolstorage.NewStorage("my-uuid", s.managedStorage, s.metadataCollection, s.txnRunner) 58 } 59 60 func (s *ToolsSuite) TearDownTest(c *gc.C) { 61 s.session.Close() 62 s.mongo.DestroyWithLog() 63 s.BaseSuite.TearDownTest(c) 64 } 65 66 func (s *ToolsSuite) TestAddTools(c *gc.C) { 67 s.testAddTools(c, "some-tools") 68 } 69 70 func (s *ToolsSuite) TestAddToolsReplaces(c *gc.C) { 71 s.testAddTools(c, "abc") 72 s.testAddTools(c, "def") 73 } 74 75 func (s *ToolsSuite) testAddTools(c *gc.C, content string) { 76 var r io.Reader = bytes.NewReader([]byte(content)) 77 addedMetadata := toolstorage.Metadata{ 78 Version: version.Current, 79 Size: int64(len(content)), 80 SHA256: "hash(" + content + ")", 81 } 82 err := s.storage.AddTools(r, addedMetadata) 83 c.Assert(err, jc.ErrorIsNil) 84 85 metadata, rc, err := s.storage.Tools(version.Current) 86 c.Assert(err, jc.ErrorIsNil) 87 c.Assert(r, gc.NotNil) 88 defer rc.Close() 89 c.Assert(metadata, gc.Equals, addedMetadata) 90 91 data, err := ioutil.ReadAll(rc) 92 c.Assert(err, jc.ErrorIsNil) 93 c.Assert(string(data), gc.Equals, content) 94 } 95 96 func bumpVersion(v version.Binary) version.Binary { 97 v.Build++ 98 return v 99 } 100 101 func (s *ToolsSuite) TestAllMetadata(c *gc.C) { 102 metadata, err := s.storage.AllMetadata() 103 c.Assert(err, jc.ErrorIsNil) 104 c.Assert(metadata, gc.HasLen, 0) 105 106 s.addMetadataDoc(c, version.Current, 3, "hash(abc)", "path") 107 metadata, err = s.storage.AllMetadata() 108 c.Assert(err, jc.ErrorIsNil) 109 c.Assert(metadata, gc.HasLen, 1) 110 expected := []toolstorage.Metadata{{ 111 Version: version.Current, 112 Size: 3, 113 SHA256: "hash(abc)", 114 }} 115 c.Assert(metadata, jc.SameContents, expected) 116 117 alias := bumpVersion(version.Current) 118 s.addMetadataDoc(c, alias, 3, "hash(abc)", "path") 119 120 metadata, err = s.storage.AllMetadata() 121 c.Assert(err, jc.ErrorIsNil) 122 c.Assert(metadata, gc.HasLen, 2) 123 expected = append(expected, toolstorage.Metadata{ 124 Version: alias, 125 Size: 3, 126 SHA256: "hash(abc)", 127 }) 128 c.Assert(metadata, jc.SameContents, expected) 129 } 130 131 func (s *ToolsSuite) TestMetadata(c *gc.C) { 132 metadata, err := s.storage.Metadata(version.Current) 133 c.Assert(err, jc.Satisfies, errors.IsNotFound) 134 135 s.addMetadataDoc(c, version.Current, 3, "hash(abc)", "path") 136 metadata, err = s.storage.Metadata(version.Current) 137 c.Assert(err, jc.ErrorIsNil) 138 c.Assert(metadata, gc.Equals, toolstorage.Metadata{ 139 Version: version.Current, 140 Size: 3, 141 SHA256: "hash(abc)", 142 }) 143 } 144 145 func (s *ToolsSuite) TestTools(c *gc.C) { 146 _, _, err := s.storage.Tools(version.Current) 147 c.Assert(err, jc.Satisfies, errors.IsNotFound) 148 c.Assert(err, gc.ErrorMatches, `.* tools metadata not found`) 149 150 s.addMetadataDoc(c, version.Current, 3, "hash(abc)", "path") 151 _, _, err = s.storage.Tools(version.Current) 152 c.Assert(err, jc.Satisfies, errors.IsNotFound) 153 c.Assert(err, gc.ErrorMatches, `resource at path "environs/my-uuid/path" not found`) 154 155 err = s.managedStorage.PutForEnvironment("my-uuid", "path", strings.NewReader("blah"), 4) 156 c.Assert(err, jc.ErrorIsNil) 157 158 metadata, r, err := s.storage.Tools(version.Current) 159 c.Assert(err, jc.ErrorIsNil) 160 defer r.Close() 161 c.Assert(metadata, gc.Equals, toolstorage.Metadata{ 162 Version: version.Current, 163 Size: 3, 164 SHA256: "hash(abc)", 165 }) 166 167 data, err := ioutil.ReadAll(r) 168 c.Assert(err, jc.ErrorIsNil) 169 c.Assert(string(data), gc.Equals, "blah") 170 } 171 172 func (s *ToolsSuite) TestAddToolsRemovesExisting(c *gc.C) { 173 // Add a metadata doc and a blob at a known path, then 174 // call AddTools and ensure the original blob is removed. 175 s.addMetadataDoc(c, version.Current, 3, "hash(abc)", "path") 176 err := s.managedStorage.PutForEnvironment("my-uuid", "path", strings.NewReader("blah"), 4) 177 c.Assert(err, jc.ErrorIsNil) 178 179 addedMetadata := toolstorage.Metadata{ 180 Version: version.Current, 181 Size: 6, 182 SHA256: "hash(xyzzzz)", 183 } 184 err = s.storage.AddTools(strings.NewReader("xyzzzz"), addedMetadata) 185 c.Assert(err, jc.ErrorIsNil) 186 187 // old blob should be gone 188 _, _, err = s.managedStorage.GetForEnvironment("my-uuid", "path") 189 c.Assert(err, jc.Satisfies, errors.IsNotFound) 190 191 s.assertTools(c, addedMetadata, "xyzzzz") 192 } 193 194 func (s *ToolsSuite) TestAddToolsRemovesExistingRemoveFails(c *gc.C) { 195 // Add a metadata doc and a blob at a known path, then 196 // call AddTools and ensure that AddTools attempts to remove 197 // the original blob, but does not return an error if it 198 // fails. 199 s.addMetadataDoc(c, version.Current, 3, "hash(abc)", "path") 200 err := s.managedStorage.PutForEnvironment("my-uuid", "path", strings.NewReader("blah"), 4) 201 c.Assert(err, jc.ErrorIsNil) 202 203 storage := toolstorage.NewStorage( 204 "my-uuid", 205 removeFailsManagedStorage{s.managedStorage}, 206 s.metadataCollection, 207 s.txnRunner, 208 ) 209 addedMetadata := toolstorage.Metadata{ 210 Version: version.Current, 211 Size: 6, 212 SHA256: "hash(xyzzzz)", 213 } 214 err = storage.AddTools(strings.NewReader("xyzzzz"), addedMetadata) 215 c.Assert(err, jc.ErrorIsNil) 216 217 // old blob should still be there 218 r, _, err := s.managedStorage.GetForEnvironment("my-uuid", "path") 219 c.Assert(err, jc.ErrorIsNil) 220 r.Close() 221 222 s.assertTools(c, addedMetadata, "xyzzzz") 223 } 224 225 func (s *ToolsSuite) TestAddToolsRemovesBlobOnFailure(c *gc.C) { 226 storage := toolstorage.NewStorage( 227 "my-uuid", 228 s.managedStorage, 229 s.metadataCollection, 230 errorTransactionRunner{s.txnRunner}, 231 ) 232 addedMetadata := toolstorage.Metadata{ 233 Version: version.Current, 234 Size: 6, 235 SHA256: "hash", 236 } 237 err := storage.AddTools(strings.NewReader("xyzzzz"), addedMetadata) 238 c.Assert(err, gc.ErrorMatches, "cannot store tools metadata: Run fails") 239 240 path := fmt.Sprintf("tools/%s-%s", addedMetadata.Version, addedMetadata.SHA256) 241 _, _, err = s.managedStorage.GetForEnvironment("my-uuid", path) 242 c.Assert(err, jc.Satisfies, errors.IsNotFound) 243 } 244 245 func (s *ToolsSuite) TestAddToolsRemovesBlobOnFailureRemoveFails(c *gc.C) { 246 storage := toolstorage.NewStorage( 247 "my-uuid", 248 removeFailsManagedStorage{s.managedStorage}, 249 s.metadataCollection, 250 errorTransactionRunner{s.txnRunner}, 251 ) 252 addedMetadata := toolstorage.Metadata{ 253 Version: version.Current, 254 Size: 6, 255 SHA256: "hash", 256 } 257 err := storage.AddTools(strings.NewReader("xyzzzz"), addedMetadata) 258 c.Assert(err, gc.ErrorMatches, "cannot store tools metadata: Run fails") 259 260 // blob should still be there, because the removal failed. 261 path := fmt.Sprintf("tools/%s-%s", addedMetadata.Version, addedMetadata.SHA256) 262 r, _, err := s.managedStorage.GetForEnvironment("my-uuid", path) 263 c.Assert(err, jc.ErrorIsNil) 264 r.Close() 265 } 266 267 func (s *ToolsSuite) TestAddToolsSame(c *gc.C) { 268 metadata := toolstorage.Metadata{Version: version.Current, Size: 1, SHA256: "0"} 269 for i := 0; i < 2; i++ { 270 err := s.storage.AddTools(strings.NewReader("0"), metadata) 271 c.Assert(err, jc.ErrorIsNil) 272 s.assertTools(c, metadata, "0") 273 } 274 } 275 276 func (s *ToolsSuite) TestAddToolsConcurrent(c *gc.C) { 277 metadata0 := toolstorage.Metadata{Version: version.Current, Size: 1, SHA256: "0"} 278 metadata1 := toolstorage.Metadata{Version: version.Current, Size: 1, SHA256: "1"} 279 280 addMetadata := func() { 281 err := s.storage.AddTools(strings.NewReader("0"), metadata0) 282 c.Assert(err, jc.ErrorIsNil) 283 r, _, err := s.managedStorage.GetForEnvironment("my-uuid", fmt.Sprintf("tools/%s-0", version.Current)) 284 c.Assert(err, jc.ErrorIsNil) 285 r.Close() 286 } 287 defer txntesting.SetBeforeHooks(c, s.txnRunner, addMetadata).Check() 288 289 err := s.storage.AddTools(strings.NewReader("1"), metadata1) 290 c.Assert(err, jc.ErrorIsNil) 291 292 // Blob added in before-hook should be removed. 293 _, _, err = s.managedStorage.GetForEnvironment("my-uuid", fmt.Sprintf("tools/%s-0", version.Current)) 294 c.Assert(err, jc.Satisfies, errors.IsNotFound) 295 296 s.assertTools(c, metadata1, "1") 297 } 298 299 func (s *ToolsSuite) TestAddToolsExcessiveContention(c *gc.C) { 300 metadata := []toolstorage.Metadata{ 301 {Version: version.Current, Size: 1, SHA256: "0"}, 302 {Version: version.Current, Size: 1, SHA256: "1"}, 303 {Version: version.Current, Size: 1, SHA256: "2"}, 304 {Version: version.Current, Size: 1, SHA256: "3"}, 305 } 306 307 i := 1 308 addMetadata := func() { 309 err := s.storage.AddTools(strings.NewReader(metadata[i].SHA256), metadata[i]) 310 c.Assert(err, jc.ErrorIsNil) 311 i++ 312 } 313 defer txntesting.SetBeforeHooks(c, s.txnRunner, addMetadata, addMetadata, addMetadata).Check() 314 315 err := s.storage.AddTools(strings.NewReader(metadata[0].SHA256), metadata[0]) 316 c.Assert(err, gc.ErrorMatches, "cannot store tools metadata: state changing too quickly; try again soon") 317 318 // There should be no blobs apart from the last one added by the before-hook. 319 for _, metadata := range metadata[:3] { 320 path := fmt.Sprintf("tools/%s-%s", metadata.Version, metadata.SHA256) 321 _, _, err = s.managedStorage.GetForEnvironment("my-uuid", path) 322 c.Assert(err, jc.Satisfies, errors.IsNotFound) 323 } 324 325 s.assertTools(c, metadata[3], "3") 326 } 327 328 func (s *ToolsSuite) addMetadataDoc(c *gc.C, v version.Binary, size int64, hash, path string) { 329 doc := struct { 330 Id string `bson:"_id"` 331 Version version.Binary `bson:"version"` 332 Size int64 `bson:"size"` 333 SHA256 string `bson:"sha256,omitempty"` 334 Path string `bson:"path"` 335 }{ 336 Id: v.String(), 337 Version: v, 338 Size: size, 339 SHA256: hash, 340 Path: path, 341 } 342 err := s.metadataCollection.Insert(&doc) 343 c.Assert(err, jc.ErrorIsNil) 344 } 345 346 func (s *ToolsSuite) assertTools(c *gc.C, expected toolstorage.Metadata, content string) { 347 metadata, r, err := s.storage.Tools(expected.Version) 348 c.Assert(err, jc.ErrorIsNil) 349 defer r.Close() 350 c.Assert(metadata, gc.Equals, expected) 351 352 data, err := ioutil.ReadAll(r) 353 c.Assert(err, jc.ErrorIsNil) 354 c.Assert(string(data), gc.Equals, content) 355 } 356 357 type removeFailsManagedStorage struct { 358 blobstore.ManagedStorage 359 } 360 361 func (removeFailsManagedStorage) RemoveForEnvironment(uuid, path string) error { 362 return errors.Errorf("cannot remove %s:%s", uuid, path) 363 } 364 365 type errorTransactionRunner struct { 366 jujutxn.Runner 367 } 368 369 func (errorTransactionRunner) Run(transactions jujutxn.TransactionSource) error { 370 return errors.New("Run fails") 371 }