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