github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/imagestorage/image_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package imagestorage_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "strings" 12 stdtesting "testing" 13 "time" // Only used for time types. 14 15 "github.com/juju/errors" 16 gitjujutesting "github.com/juju/testing" 17 jc "github.com/juju/testing/checkers" 18 "github.com/juju/txn" 19 jujutxn "github.com/juju/txn" 20 txntesting "github.com/juju/txn/testing" 21 gc "gopkg.in/check.v1" 22 "gopkg.in/mgo.v2" 23 24 "github.com/juju/juju/state/imagestorage" 25 "github.com/juju/juju/testing" 26 ) 27 28 var _ = gc.Suite(&ImageSuite{}) 29 30 func TestPackage(t *stdtesting.T) { 31 gc.TestingT(t) 32 } 33 34 type ImageSuite struct { 35 testing.BaseSuite 36 mongo *gitjujutesting.MgoInstance 37 session *mgo.Session 38 storage imagestorage.Storage 39 metadataCollection *mgo.Collection 40 txnRunner jujutxn.Runner 41 } 42 43 func (s *ImageSuite) SetUpTest(c *gc.C) { 44 s.BaseSuite.SetUpTest(c) 45 s.mongo = &gitjujutesting.MgoInstance{} 46 s.mongo.Start(nil) 47 48 var err error 49 s.session, err = s.mongo.Dial() 50 c.Assert(err, gc.IsNil) 51 s.storage = imagestorage.NewStorage(s.session, "my-uuid") 52 s.metadataCollection = imagestorage.MetadataCollection(s.storage) 53 s.txnRunner = jujutxn.NewRunner(jujutxn.RunnerParams{Database: s.metadataCollection.Database}) 54 s.patchTransactionRunner() 55 } 56 57 func (s *ImageSuite) TearDownTest(c *gc.C) { 58 s.session.Close() 59 s.mongo.DestroyWithLog() 60 s.BaseSuite.TearDownTest(c) 61 } 62 func (s *ImageSuite) patchTransactionRunner() { 63 s.PatchValue(imagestorage.TxnRunner, func(db *mgo.Database) txn.Runner { 64 return s.txnRunner 65 }) 66 } 67 68 func (s *ImageSuite) TestAddImage(c *gc.C) { 69 s.testAddImage(c, "some-image") 70 } 71 72 func (s *ImageSuite) TestAddImageReplaces(c *gc.C) { 73 s.testAddImage(c, "abc") 74 s.testAddImage(c, "defghi") 75 } 76 77 func checkMetadata(c *gc.C, fromDb, metadata *imagestorage.Metadata) { 78 c.Assert(fromDb.Created.IsZero(), jc.IsFalse) 79 // We don't want Now() here, we want NonZeroTime().Add(...). Before 80 // that can happen, we need to look at AddImage for its Created 81 // timestamp. 82 c.Assert(fromDb.Created.Before(time.Now()), jc.IsTrue) 83 fromDb.Created = testing.ZeroTime() 84 c.Assert(metadata, gc.DeepEquals, fromDb) 85 } 86 87 func checkAllMetadata(c *gc.C, fromDb []*imagestorage.Metadata, metadata ...*imagestorage.Metadata) { 88 c.Assert(len(metadata), gc.Equals, len(fromDb)) 89 for i, m := range metadata { 90 checkMetadata(c, fromDb[i], m) 91 } 92 } 93 94 func (s *ImageSuite) testAddImage(c *gc.C, content string) { 95 var r io.Reader = bytes.NewReader([]byte(content)) 96 addedMetadata := &imagestorage.Metadata{ 97 ModelUUID: "my-uuid", 98 Kind: "lxc", 99 Series: "trusty", 100 Arch: "amd64", 101 Size: int64(len(content)), 102 SHA256: "hash(" + content + ")", 103 SourceURL: "http://path", 104 } 105 err := s.storage.AddImage(r, addedMetadata) 106 c.Assert(err, gc.IsNil) 107 108 metadata, rc, err := s.storage.Image("lxc", "trusty", "amd64") 109 c.Assert(err, gc.IsNil) 110 c.Assert(r, gc.NotNil) 111 defer rc.Close() 112 checkMetadata(c, metadata, addedMetadata) 113 114 data, err := ioutil.ReadAll(rc) 115 c.Assert(err, gc.IsNil) 116 c.Assert(string(data), gc.Equals, content) 117 } 118 119 func (s *ImageSuite) TestImage(c *gc.C) { 120 _, _, err := s.storage.Image("lxc", "trusty", "amd64") 121 c.Assert(err, jc.Satisfies, errors.IsNotFound) 122 c.Assert(err, gc.ErrorMatches, `.* image metadata not found`) 123 124 s.addMetadataDoc(c, "lxc", "trusty", "amd64", 3, "hash(abc)", "path", "http://path") 125 _, _, err = s.storage.Image("lxc", "trusty", "amd64") 126 c.Assert(err, jc.Satisfies, errors.IsNotFound) 127 c.Assert(err, gc.ErrorMatches, `resource at path "buckets/my-uuid/path" not found`) 128 129 managedStorage := imagestorage.ManagedStorage(s.storage, s.session) 130 err = managedStorage.PutForBucket("my-uuid", "path", strings.NewReader("blah"), 4) 131 c.Assert(err, gc.IsNil) 132 133 metadata, r, err := s.storage.Image("lxc", "trusty", "amd64") 134 c.Assert(err, gc.IsNil) 135 defer r.Close() 136 checkMetadata(c, metadata, &imagestorage.Metadata{ 137 ModelUUID: "my-uuid", 138 Kind: "lxc", 139 Series: "trusty", 140 Arch: "amd64", 141 Size: 3, 142 SHA256: "hash(abc)", 143 SourceURL: "http://path", 144 }) 145 146 data, err := ioutil.ReadAll(r) 147 c.Assert(err, gc.IsNil) 148 c.Assert(string(data), gc.Equals, "blah") 149 } 150 151 func (s *ImageSuite) TestAddImageRemovesExisting(c *gc.C) { 152 // Add a metadata doc and a blob at a known path, then 153 // call AddImage and ensure the original blob is removed. 154 s.addMetadataDoc(c, "lxc", "trusty", "amd64", 3, "hash(abc)", "path", "http://path") 155 managedStorage := imagestorage.ManagedStorage(s.storage, s.session) 156 err := managedStorage.PutForBucket("my-uuid", "path", strings.NewReader("blah"), 4) 157 c.Assert(err, gc.IsNil) 158 159 addedMetadata := &imagestorage.Metadata{ 160 ModelUUID: "my-uuid", 161 Kind: "lxc", 162 Series: "trusty", 163 Arch: "amd64", 164 Size: 6, 165 SHA256: "hash(xyzzzz)", 166 SourceURL: "http://path", 167 } 168 err = s.storage.AddImage(strings.NewReader("xyzzzz"), addedMetadata) 169 c.Assert(err, gc.IsNil) 170 171 // old blob should be gone 172 _, _, err = managedStorage.GetForBucket("my-uuid", "path") 173 c.Assert(err, jc.Satisfies, errors.IsNotFound) 174 175 s.assertImage(c, addedMetadata, "xyzzzz") 176 } 177 178 func (s *ImageSuite) TestAddImageRemovesExistingRemoveFails(c *gc.C) { 179 // Add a metadata doc and a blob at a known path, then 180 // call AddImage and ensure that AddImage attempts to remove 181 // the original blob, but does not return an error if it 182 // fails. 183 s.addMetadataDoc(c, "lxc", "trusty", "amd64", 3, "hash(abc)", "path", "http://path") 184 managedStorage := imagestorage.ManagedStorage(s.storage, s.session) 185 err := managedStorage.PutForBucket("my-uuid", "path", strings.NewReader("blah"), 4) 186 c.Assert(err, gc.IsNil) 187 188 storage := imagestorage.NewStorage(s.session, "my-uuid") 189 s.PatchValue(imagestorage.GetManagedStorage, imagestorage.RemoveFailsManagedStorage) 190 addedMetadata := &imagestorage.Metadata{ 191 ModelUUID: "my-uuid", 192 Kind: "lxc", 193 Series: "trusty", 194 Arch: "amd64", 195 Size: 6, 196 SHA256: "hash(xyzzzz)", 197 SourceURL: "http://path", 198 } 199 err = storage.AddImage(strings.NewReader("xyzzzz"), addedMetadata) 200 c.Assert(err, gc.IsNil) 201 202 // old blob should still be there 203 r, _, err := managedStorage.GetForBucket("my-uuid", "path") 204 c.Assert(err, gc.IsNil) 205 r.Close() 206 207 s.assertImage(c, addedMetadata, "xyzzzz") 208 } 209 210 type errorTransactionRunner struct { 211 txn.Runner 212 } 213 214 func (errorTransactionRunner) Run(transactions txn.TransactionSource) error { 215 return errors.New("Run fails") 216 } 217 218 func (s *ImageSuite) TestAddImageRemovesBlobOnFailure(c *gc.C) { 219 storage := imagestorage.NewStorage(s.session, "my-uuid") 220 s.txnRunner = errorTransactionRunner{s.txnRunner} 221 addedMetadata := &imagestorage.Metadata{ 222 ModelUUID: "my-uuid", 223 Kind: "lxc", 224 Series: "trusty", 225 Arch: "amd64", 226 Size: 6, 227 SHA256: "hash", 228 } 229 err := storage.AddImage(strings.NewReader("xyzzzz"), addedMetadata) 230 c.Assert(err, gc.ErrorMatches, "cannot store image metadata: Run fails") 231 232 path := fmt.Sprintf( 233 "images/%s-%s-%s:%s", addedMetadata.Kind, addedMetadata.Series, addedMetadata.Arch, addedMetadata.SHA256) 234 managedStorage := imagestorage.ManagedStorage(s.storage, s.session) 235 _, _, err = managedStorage.GetForBucket("my-uuid", path) 236 c.Assert(err, jc.Satisfies, errors.IsNotFound) 237 } 238 239 func (s *ImageSuite) TestAddImageRemovesBlobOnFailureRemoveFails(c *gc.C) { 240 storage := imagestorage.NewStorage(s.session, "my-uuid") 241 s.PatchValue(imagestorage.GetManagedStorage, imagestorage.RemoveFailsManagedStorage) 242 s.txnRunner = errorTransactionRunner{s.txnRunner} 243 addedMetadata := &imagestorage.Metadata{ 244 ModelUUID: "my-uuid", 245 Kind: "lxc", 246 Series: "trusty", 247 Arch: "amd64", 248 Size: 6, 249 SHA256: "hash", 250 } 251 err := storage.AddImage(strings.NewReader("xyzzzz"), addedMetadata) 252 c.Assert(err, gc.ErrorMatches, "cannot store image metadata: Run fails") 253 254 // blob should still be there, because the removal failed. 255 path := fmt.Sprintf( 256 "images/%s-%s-%s:%s", addedMetadata.Kind, addedMetadata.Series, addedMetadata.Arch, addedMetadata.SHA256) 257 managedStorage := imagestorage.ManagedStorage(s.storage, s.session) 258 r, _, err := managedStorage.GetForBucket("my-uuid", path) 259 c.Assert(err, gc.IsNil) 260 r.Close() 261 } 262 263 func (s *ImageSuite) TestAddImageSame(c *gc.C) { 264 metadata := &imagestorage.Metadata{ 265 ModelUUID: "my-uuid", Kind: "lxc", Series: "trusty", Arch: "amd64", Size: 1, SHA256: "0", SourceURL: "http://path", 266 } 267 for i := 0; i < 2; i++ { 268 err := s.storage.AddImage(strings.NewReader("0"), metadata) 269 c.Assert(err, gc.IsNil) 270 s.assertImage(c, metadata, "0") 271 } 272 } 273 274 func (s *ImageSuite) TestAddImageAndJustMetadataExists(c *gc.C) { 275 s.addMetadataDoc(c, "lxc", "trusty", "amd64", 3, "hash(abc)", "images/lxc-trusty-amd64:hash(abc)", "http://path") 276 n, err := s.metadataCollection.Count() 277 c.Assert(err, jc.ErrorIsNil) 278 c.Assert(n, gc.Equals, 1) 279 s.testAddImage(c, "abc") 280 n, err = s.metadataCollection.Count() 281 c.Assert(err, jc.ErrorIsNil) 282 c.Assert(n, gc.Equals, 1) 283 } 284 285 func (s *ImageSuite) TestJustMetadataFails(c *gc.C) { 286 s.addMetadataDoc(c, "lxc", "trusty", "amd64", 3, "hash(abc)", "images/lxc-trusty-amd64:hash(abc)", "http://path") 287 _, rc, err := s.storage.Image("lxc", "trusty", "amd64") 288 c.Assert(rc, gc.IsNil) 289 c.Assert(err, gc.NotNil) 290 } 291 292 func (s *ImageSuite) TestAddImageConcurrent(c *gc.C) { 293 metadata0 := &imagestorage.Metadata{ 294 ModelUUID: "my-uuid", Kind: "lxc", Series: "trusty", Arch: "amd64", Size: 1, SHA256: "0", SourceURL: "http://path", 295 } 296 metadata1 := &imagestorage.Metadata{ 297 ModelUUID: "my-uuid", Kind: "lxc", Series: "trusty", Arch: "amd64", Size: 1, SHA256: "1", SourceURL: "http://path", 298 } 299 300 addMetadata := func() { 301 err := s.storage.AddImage(strings.NewReader("0"), metadata0) 302 c.Assert(err, gc.IsNil) 303 managedStorage := imagestorage.ManagedStorage(s.storage, s.session) 304 r, _, err := managedStorage.GetForBucket("my-uuid", "images/lxc-trusty-amd64:0") 305 c.Assert(err, gc.IsNil) 306 r.Close() 307 } 308 defer txntesting.SetBeforeHooks(c, s.txnRunner, addMetadata).Check() 309 310 err := s.storage.AddImage(strings.NewReader("1"), metadata1) 311 c.Assert(err, gc.IsNil) 312 313 // Blob added in before-hook should be removed. 314 managedStorage := imagestorage.ManagedStorage(s.storage, s.session) 315 _, _, err = managedStorage.GetForBucket("my-uuid", "images/lxc-trusty-amd64:0") 316 c.Assert(err, jc.Satisfies, errors.IsNotFound) 317 318 s.assertImage(c, metadata1, "1") 319 } 320 321 func (s *ImageSuite) TestAddImageExcessiveContention(c *gc.C) { 322 metadata := []*imagestorage.Metadata{ 323 {ModelUUID: "my-uuid", Kind: "lxc", Series: "trusty", Arch: "amd64", Size: 1, SHA256: "0", SourceURL: "http://path"}, 324 {ModelUUID: "my-uuid", Kind: "lxc", Series: "trusty", Arch: "amd64", Size: 1, SHA256: "1", SourceURL: "http://path"}, 325 {ModelUUID: "my-uuid", Kind: "lxc", Series: "trusty", Arch: "amd64", Size: 1, SHA256: "2", SourceURL: "http://path"}, 326 {ModelUUID: "my-uuid", Kind: "lxc", Series: "trusty", Arch: "amd64", Size: 1, SHA256: "3", SourceURL: "http://path"}, 327 } 328 329 i := 1 330 addMetadata := func() { 331 err := s.storage.AddImage(strings.NewReader(metadata[i].SHA256), metadata[i]) 332 c.Assert(err, gc.IsNil) 333 i++ 334 } 335 defer txntesting.SetBeforeHooks(c, s.txnRunner, addMetadata, addMetadata, addMetadata).Check() 336 337 err := s.storage.AddImage(strings.NewReader(metadata[0].SHA256), metadata[0]) 338 c.Assert(err, gc.ErrorMatches, "cannot store image metadata: state changing too quickly; try again soon") 339 340 // There should be no blobs apart from the last one added by the before-hook. 341 for _, metadata := range metadata[:3] { 342 path := fmt.Sprintf("images/%s-%s-%s:%s", metadata.Kind, metadata.Series, metadata.Arch, metadata.SHA256) 343 managedStorage := imagestorage.ManagedStorage(s.storage, s.session) 344 _, _, err = managedStorage.GetForBucket("my-uuid", path) 345 c.Assert(err, jc.Satisfies, errors.IsNotFound) 346 } 347 348 s.assertImage(c, metadata[3], "3") 349 } 350 351 func (s *ImageSuite) TestDeleteImage(c *gc.C) { 352 s.addMetadataDoc(c, "lxc", "trusty", "amd64", 3, "hash(abc)", "images/lxc-trusty-amd64:sha256", "http://lxc-trusty-amd64") 353 managedStorage := imagestorage.ManagedStorage(s.storage, s.session) 354 err := managedStorage.PutForBucket("my-uuid", "images/lxc-trusty-amd64:sha256", strings.NewReader("blah"), 4) 355 c.Assert(err, gc.IsNil) 356 357 _, rc, err := s.storage.Image("lxc", "trusty", "amd64") 358 c.Assert(err, gc.IsNil) 359 c.Assert(rc, gc.NotNil) 360 rc.Close() 361 362 metadata := &imagestorage.Metadata{ 363 ModelUUID: "my-uuid", 364 Kind: "lxc", 365 Series: "trusty", 366 Arch: "amd64", 367 SHA256: "sha256", 368 } 369 err = s.storage.DeleteImage(metadata) 370 c.Assert(err, gc.IsNil) 371 372 _, _, err = managedStorage.GetForBucket("my-uuid", "images/lxc-trusty-amd64:sha256") 373 c.Assert(err, jc.Satisfies, errors.IsNotFound) 374 375 _, _, err = s.storage.Image("lxc", "trusty", "amd64") 376 c.Assert(err, jc.Satisfies, errors.IsNotFound) 377 } 378 379 func (s *ImageSuite) TestDeleteNotExistentImage(c *gc.C) { 380 metadata := &imagestorage.Metadata{ 381 ModelUUID: "my-uuid", 382 Kind: "lxc", 383 Series: "trusty", 384 Arch: "amd64", 385 SHA256: "sha256", 386 } 387 err := s.storage.DeleteImage(metadata) 388 c.Assert(err, jc.Satisfies, errors.IsNotFound) 389 } 390 391 func (s *ImageSuite) addMetadataDoc(c *gc.C, kind, series, arch string, size int64, checksum, path, sourceURL string) { 392 doc := struct { 393 Id string `bson:"_id"` 394 ModelUUID string `bson:"modelUUID"` 395 Kind string `bson:"kind"` 396 Series string `bson:"series"` 397 Arch string `bson:"arch"` 398 Size int64 `bson:"size"` 399 SHA256 string `bson:"sha256,omitempty"` 400 Path string `bson:"path"` 401 Created time.Time `bson:"created"` 402 SourceURL string `bson:"sourceurl"` 403 }{ 404 Id: fmt.Sprintf("my-uuid-%s-%s-%s", kind, series, arch), 405 ModelUUID: "my-uuid", 406 Kind: kind, 407 Series: series, 408 Arch: arch, 409 Size: size, 410 SHA256: checksum, 411 Path: path, 412 Created: testing.NonZeroTime(), 413 SourceURL: sourceURL, 414 } 415 err := s.metadataCollection.Insert(&doc) 416 c.Assert(err, gc.IsNil) 417 } 418 419 func (s *ImageSuite) assertImage(c *gc.C, expected *imagestorage.Metadata, content string) { 420 metadata, r, err := s.storage.Image(expected.Kind, expected.Series, expected.Arch) 421 c.Assert(err, gc.IsNil) 422 defer r.Close() 423 checkMetadata(c, metadata, expected) 424 425 data, err := ioutil.ReadAll(r) 426 c.Assert(err, gc.IsNil) 427 c.Assert(string(data), gc.Equals, content) 428 } 429 430 func (s *ImageSuite) createListImageMetadata(c *gc.C) []*imagestorage.Metadata { 431 s.addMetadataDoc(c, "lxc", "trusty", "amd64", 3, "hash(abc)", "images/lxc-trusty-amd64:sha256", "http://lxc-trusty-amd64") 432 metadataLxc := &imagestorage.Metadata{ 433 ModelUUID: "my-uuid", 434 Kind: "lxc", 435 Series: "trusty", 436 Arch: "amd64", 437 SHA256: "hash(abc)", 438 Size: 3, 439 SourceURL: "http://lxc-trusty-amd64", 440 } 441 s.addMetadataDoc(c, "kvm", "precise", "amd64", 4, "hash(abcd)", "images/kvm-precise-amd64:sha256", "http://kvm-precise-amd64") 442 metadataKvm := &imagestorage.Metadata{ 443 ModelUUID: "my-uuid", 444 Kind: "kvm", 445 Series: "precise", 446 Arch: "amd64", 447 SHA256: "hash(abcd)", 448 Size: 4, 449 SourceURL: "http://kvm-precise-amd64", 450 } 451 return []*imagestorage.Metadata{metadataLxc, metadataKvm} 452 } 453 454 func (s *ImageSuite) TestListAllImages(c *gc.C) { 455 testMetadata := s.createListImageMetadata(c) 456 metadata, err := s.storage.ListImages(imagestorage.ImageFilter{}) 457 c.Assert(err, gc.IsNil) 458 checkAllMetadata(c, metadata, testMetadata...) 459 } 460 461 func (s *ImageSuite) TestListImagesByKind(c *gc.C) { 462 testMetadata := s.createListImageMetadata(c) 463 metadata, err := s.storage.ListImages(imagestorage.ImageFilter{Kind: "lxc"}) 464 c.Assert(err, gc.IsNil) 465 checkAllMetadata(c, metadata, testMetadata[0]) 466 } 467 468 func (s *ImageSuite) TestListImagesBySeries(c *gc.C) { 469 testMetadata := s.createListImageMetadata(c) 470 metadata, err := s.storage.ListImages(imagestorage.ImageFilter{Series: "precise"}) 471 c.Assert(err, gc.IsNil) 472 checkAllMetadata(c, metadata, testMetadata[1]) 473 } 474 475 func (s *ImageSuite) TestListImagesByArch(c *gc.C) { 476 testMetadata := s.createListImageMetadata(c) 477 metadata, err := s.storage.ListImages(imagestorage.ImageFilter{Arch: "amd64"}) 478 c.Assert(err, gc.IsNil) 479 checkAllMetadata(c, metadata, testMetadata...) 480 } 481 482 func (s *ImageSuite) TestListImagesNoMatch(c *gc.C) { 483 metadata, err := s.storage.ListImages(imagestorage.ImageFilter{Series: "utopic"}) 484 c.Assert(err, gc.IsNil) 485 checkAllMetadata(c, metadata) 486 } 487 488 func (s *ImageSuite) TestListImagesMultiFilter(c *gc.C) { 489 testMetadata := s.createListImageMetadata(c) 490 metadata, err := s.storage.ListImages(imagestorage.ImageFilter{Series: "trusty", Arch: "amd64"}) 491 c.Assert(err, gc.IsNil) 492 checkAllMetadata(c, metadata, testMetadata[0]) 493 }