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