github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/storage_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "fmt" 8 "sort" 9 10 "github.com/juju/errors" 11 jc "github.com/juju/testing/checkers" 12 "github.com/juju/utils/set" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/juju/charm.v6-unstable" 15 "gopkg.in/juju/names.v2" 16 "gopkg.in/mgo.v2" 17 18 "github.com/juju/juju/provider/dummy" 19 "github.com/juju/juju/state" 20 "github.com/juju/juju/state/testing" 21 "github.com/juju/juju/storage" 22 "github.com/juju/juju/storage/poolmanager" 23 "github.com/juju/juju/storage/provider" 24 dummystorage "github.com/juju/juju/storage/provider/dummy" 25 "github.com/juju/juju/testing/factory" 26 ) 27 28 type StorageStateSuiteBase struct { 29 ConnSuite 30 } 31 32 func (s *StorageStateSuiteBase) SetUpTest(c *gc.C) { 33 s.ConnSuite.SetUpTest(c) 34 35 // Create a default pool for block devices. 36 pm := poolmanager.New(state.NewStateSettings(s.State), dummy.StorageProviders()) 37 _, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{}) 38 c.Assert(err, jc.ErrorIsNil) 39 40 // Create a pool that creates persistent block devices. 41 _, err = pm.Create("persistent-block", "environscoped-block", map[string]interface{}{ 42 "persistent": true, 43 }) 44 c.Assert(err, jc.ErrorIsNil) 45 } 46 47 func (s *StorageStateSuiteBase) setupSingleStorage(c *gc.C, kind, pool string) (*state.Application, *state.Unit, names.StorageTag) { 48 // There are test charms called "storage-block" and 49 // "storage-filesystem" which are what you'd expect. 50 ch := s.AddTestingCharm(c, "storage-"+kind) 51 storage := map[string]state.StorageConstraints{ 52 "data": makeStorageCons(pool, 1024, 1), 53 } 54 service := s.AddTestingServiceWithStorage(c, "storage-"+kind, ch, storage) 55 unit, err := service.AddUnit() 56 c.Assert(err, jc.ErrorIsNil) 57 storageTag := names.NewStorageTag("data/0") 58 return service, unit, storageTag 59 } 60 61 func (s *StorageStateSuiteBase) createStorageCharm(c *gc.C, charmName string, storageMeta charm.Storage) *state.Charm { 62 return s.createStorageCharmRev(c, charmName, storageMeta, 1) 63 } 64 65 func (s *StorageStateSuiteBase) createStorageCharmRev(c *gc.C, charmName string, storageMeta charm.Storage, rev int) *state.Charm { 66 meta := fmt.Sprintf(` 67 name: %s 68 summary: A charm for testing storage 69 description: ditto 70 storage: 71 %s: 72 type: %s 73 `, charmName, storageMeta.Name, storageMeta.Type) 74 if storageMeta.ReadOnly { 75 meta += " read-only: true\n" 76 } 77 if storageMeta.Shared { 78 meta += " shared: true\n" 79 } 80 if storageMeta.MinimumSize > 0 { 81 meta += fmt.Sprintf(" minimum-size: %dM\n", storageMeta.MinimumSize) 82 } 83 if storageMeta.Location != "" { 84 meta += " location: " + storageMeta.Location + "\n" 85 } 86 if storageMeta.CountMin != 1 || storageMeta.CountMax != 1 { 87 meta += " multiple:\n" 88 meta += fmt.Sprintf(" range: %d-", storageMeta.CountMin) 89 if storageMeta.CountMax >= 0 { 90 meta += fmt.Sprint(storageMeta.CountMax) 91 } 92 meta += "\n" 93 } 94 ch := s.AddMetaCharm(c, charmName, meta, rev) 95 return ch 96 } 97 98 func (s *StorageStateSuiteBase) setupMixedScopeStorageService(c *gc.C, kind string) *state.Application { 99 storageCons := map[string]state.StorageConstraints{ 100 "multi1to10": makeStorageCons("environscoped", 1024, 1), 101 "multi2up": makeStorageCons("machinescoped", 2048, 2), 102 } 103 ch := s.AddTestingCharm(c, "storage-"+kind+"2") 104 return s.AddTestingServiceWithStorage(c, "storage-"+kind+"2", ch, storageCons) 105 } 106 107 func (s *StorageStateSuiteBase) storageInstanceExists(c *gc.C, tag names.StorageTag) bool { 108 _, err := state.TxnRevno( 109 s.State, 110 state.StorageInstancesC, 111 state.DocID(s.State, tag.Id()), 112 ) 113 if err != nil { 114 c.Assert(err, gc.Equals, mgo.ErrNotFound) 115 return false 116 } 117 return true 118 } 119 120 func (s *StorageStateSuiteBase) assertFilesystemUnprovisioned(c *gc.C, tag names.FilesystemTag) { 121 filesystem := s.filesystem(c, tag) 122 _, err := filesystem.Info() 123 c.Assert(err, jc.Satisfies, errors.IsNotProvisioned) 124 _, ok := filesystem.Params() 125 c.Assert(ok, jc.IsTrue) 126 } 127 128 func (s *StorageStateSuiteBase) assertFilesystemInfo(c *gc.C, tag names.FilesystemTag, expect state.FilesystemInfo) { 129 filesystem := s.filesystem(c, tag) 130 info, err := filesystem.Info() 131 c.Assert(err, jc.ErrorIsNil) 132 c.Assert(info, jc.DeepEquals, expect) 133 _, ok := filesystem.Params() 134 c.Assert(ok, jc.IsFalse) 135 } 136 137 func (s *StorageStateSuiteBase) assertFilesystemAttachmentUnprovisioned(c *gc.C, m names.MachineTag, f names.FilesystemTag) { 138 filesystemAttachment := s.filesystemAttachment(c, m, f) 139 _, err := filesystemAttachment.Info() 140 c.Assert(err, jc.Satisfies, errors.IsNotProvisioned) 141 _, ok := filesystemAttachment.Params() 142 c.Assert(ok, jc.IsTrue) 143 } 144 145 func (s *StorageStateSuiteBase) assertFilesystemAttachmentInfo(c *gc.C, m names.MachineTag, f names.FilesystemTag, expect state.FilesystemAttachmentInfo) { 146 filesystemAttachment := s.filesystemAttachment(c, m, f) 147 info, err := filesystemAttachment.Info() 148 c.Assert(err, jc.ErrorIsNil) 149 c.Assert(info, jc.DeepEquals, expect) 150 _, ok := filesystemAttachment.Params() 151 c.Assert(ok, jc.IsFalse) 152 } 153 154 func (s *StorageStateSuiteBase) assertVolumeUnprovisioned(c *gc.C, tag names.VolumeTag) { 155 volume := s.volume(c, tag) 156 _, err := volume.Info() 157 c.Assert(err, jc.Satisfies, errors.IsNotProvisioned) 158 _, ok := volume.Params() 159 c.Assert(ok, jc.IsTrue) 160 } 161 162 func (s *StorageStateSuiteBase) assertVolumeInfo(c *gc.C, tag names.VolumeTag, expect state.VolumeInfo) { 163 volume := s.volume(c, tag) 164 info, err := volume.Info() 165 c.Assert(err, jc.ErrorIsNil) 166 c.Assert(info, jc.DeepEquals, expect) 167 _, ok := volume.Params() 168 c.Assert(ok, jc.IsFalse) 169 } 170 171 func (s *StorageStateSuiteBase) machine(c *gc.C, id string) *state.Machine { 172 machine, err := s.State.Machine(id) 173 c.Assert(err, jc.ErrorIsNil) 174 return machine 175 } 176 177 func (s *StorageStateSuiteBase) filesystem(c *gc.C, tag names.FilesystemTag) state.Filesystem { 178 filesystem, err := s.State.Filesystem(tag) 179 c.Assert(err, jc.ErrorIsNil) 180 return filesystem 181 } 182 183 func (s *StorageStateSuiteBase) filesystemVolume(c *gc.C, tag names.FilesystemTag) state.Volume { 184 filesystem := s.filesystem(c, tag) 185 volumeTag, err := filesystem.Volume() 186 c.Assert(err, jc.ErrorIsNil) 187 return s.volume(c, volumeTag) 188 } 189 190 func (s *StorageStateSuiteBase) filesystemAttachment(c *gc.C, m names.MachineTag, f names.FilesystemTag) state.FilesystemAttachment { 191 attachment, err := s.State.FilesystemAttachment(m, f) 192 c.Assert(err, jc.ErrorIsNil) 193 return attachment 194 } 195 196 func (s *StorageStateSuiteBase) volume(c *gc.C, tag names.VolumeTag) state.Volume { 197 volume, err := s.State.Volume(tag) 198 c.Assert(err, jc.ErrorIsNil) 199 return volume 200 } 201 202 func (s *StorageStateSuiteBase) volumeFilesystem(c *gc.C, tag names.VolumeTag) state.Filesystem { 203 filesystem, err := s.State.VolumeFilesystem(tag) 204 c.Assert(err, jc.ErrorIsNil) 205 return filesystem 206 } 207 208 func (s *StorageStateSuiteBase) volumeAttachment(c *gc.C, m names.MachineTag, v names.VolumeTag) state.VolumeAttachment { 209 attachment, err := s.State.VolumeAttachment(m, v) 210 c.Assert(err, jc.ErrorIsNil) 211 return attachment 212 } 213 214 func (s *StorageStateSuiteBase) storageInstanceVolume(c *gc.C, tag names.StorageTag) state.Volume { 215 volume, err := s.State.StorageInstanceVolume(tag) 216 c.Assert(err, jc.ErrorIsNil) 217 return volume 218 } 219 220 func (s *StorageStateSuiteBase) storageInstanceFilesystem(c *gc.C, tag names.StorageTag) state.Filesystem { 221 filesystem, err := s.State.StorageInstanceFilesystem(tag) 222 c.Assert(err, jc.ErrorIsNil) 223 return filesystem 224 } 225 226 func (s *StorageStateSuiteBase) obliterateUnit(c *gc.C, tag names.UnitTag) { 227 u, err := s.State.Unit(tag.Id()) 228 c.Assert(err, jc.ErrorIsNil) 229 err = u.Destroy() 230 c.Assert(err, jc.ErrorIsNil) 231 s.obliterateUnitStorage(c, tag) 232 err = u.EnsureDead() 233 c.Assert(err, jc.ErrorIsNil) 234 err = u.Remove() 235 c.Assert(err, jc.ErrorIsNil) 236 } 237 238 func (s *StorageStateSuiteBase) obliterateUnitStorage(c *gc.C, tag names.UnitTag) { 239 attachments, err := s.State.UnitStorageAttachments(tag) 240 c.Assert(err, jc.ErrorIsNil) 241 for _, a := range attachments { 242 err = s.State.DestroyStorageAttachment(a.StorageInstance(), a.Unit()) 243 c.Assert(err, jc.ErrorIsNil) 244 err = s.State.RemoveStorageAttachment(a.StorageInstance(), a.Unit()) 245 c.Assert(err, jc.ErrorIsNil) 246 } 247 } 248 249 func (s *StorageStateSuiteBase) obliterateVolume(c *gc.C, tag names.VolumeTag) { 250 err := s.State.DestroyVolume(tag) 251 if errors.IsNotFound(err) { 252 return 253 } 254 attachments, err := s.State.VolumeAttachments(tag) 255 c.Assert(err, jc.ErrorIsNil) 256 for _, a := range attachments { 257 s.obliterateVolumeAttachment(c, a.Machine(), a.Volume()) 258 } 259 err = s.State.RemoveVolume(tag) 260 c.Assert(err, jc.ErrorIsNil) 261 } 262 263 func (s *StorageStateSuiteBase) obliterateVolumeAttachment(c *gc.C, m names.MachineTag, v names.VolumeTag) { 264 err := s.State.DetachVolume(m, v) 265 c.Assert(err, jc.ErrorIsNil) 266 err = s.State.RemoveVolumeAttachment(m, v) 267 c.Assert(err, jc.ErrorIsNil) 268 } 269 270 func (s *StorageStateSuiteBase) obliterateFilesystem(c *gc.C, tag names.FilesystemTag) { 271 err := s.State.DestroyFilesystem(tag) 272 if errors.IsNotFound(err) { 273 return 274 } 275 attachments, err := s.State.FilesystemAttachments(tag) 276 c.Assert(err, jc.ErrorIsNil) 277 for _, a := range attachments { 278 s.obliterateFilesystemAttachment(c, a.Machine(), a.Filesystem()) 279 } 280 err = s.State.RemoveFilesystem(tag) 281 c.Assert(err, jc.ErrorIsNil) 282 } 283 284 func (s *StorageStateSuiteBase) obliterateFilesystemAttachment(c *gc.C, m names.MachineTag, f names.FilesystemTag) { 285 err := s.State.DetachFilesystem(m, f) 286 c.Assert(err, jc.ErrorIsNil) 287 err = s.State.RemoveFilesystemAttachment(m, f) 288 c.Assert(err, jc.ErrorIsNil) 289 } 290 291 // assertMachineStorageRefs ensures that the specified machine's set of volume 292 // and filesystem references corresponds exactly to the volume and filesystem 293 // attachments that relate to the machine. 294 func assertMachineStorageRefs(c *gc.C, st *state.State, m names.MachineTag) { 295 machines, closer := state.GetRawCollection(st, state.MachinesC) 296 defer closer() 297 298 var doc struct { 299 Volumes []string `bson:"volumes,omitempty"` 300 Filesystems []string `bson:"filesystems,omitempty"` 301 } 302 err := machines.FindId(state.DocID(st, m.Id())).One(&doc) 303 c.Assert(err, jc.ErrorIsNil) 304 305 have := make(set.Tags) 306 for _, v := range doc.Volumes { 307 have.Add(names.NewVolumeTag(v)) 308 } 309 for _, f := range doc.Filesystems { 310 have.Add(names.NewFilesystemTag(f)) 311 } 312 313 expect := make(set.Tags) 314 volumeAttachments, err := st.MachineVolumeAttachments(m) 315 c.Assert(err, jc.ErrorIsNil) 316 for _, a := range volumeAttachments { 317 expect.Add(a.Volume()) 318 } 319 filesystemAttachments, err := st.MachineFilesystemAttachments(m) 320 c.Assert(err, jc.ErrorIsNil) 321 for _, a := range filesystemAttachments { 322 expect.Add(a.Filesystem()) 323 } 324 325 c.Assert(have, jc.DeepEquals, expect) 326 } 327 328 func makeStorageCons(pool string, size, count uint64) state.StorageConstraints { 329 return state.StorageConstraints{Pool: pool, Size: size, Count: count} 330 } 331 332 type StorageStateSuite struct { 333 StorageStateSuiteBase 334 } 335 336 var _ = gc.Suite(&StorageStateSuite{}) 337 338 func (s *StorageStateSuite) TestAddServiceStorageConstraintsDefault(c *gc.C) { 339 ch := s.AddTestingCharm(c, "storage-block") 340 storageBlock, err := s.State.AddApplication(state.AddApplicationArgs{Name: "storage-block", Charm: ch}) 341 c.Assert(err, jc.ErrorIsNil) 342 constraints, err := storageBlock.StorageConstraints() 343 c.Assert(err, jc.ErrorIsNil) 344 c.Assert(constraints, jc.DeepEquals, map[string]state.StorageConstraints{ 345 "data": { 346 Pool: "loop", 347 Count: 1, 348 Size: 1024, 349 }, 350 "allecto": { 351 Pool: "loop", 352 Count: 0, 353 Size: 1024, 354 }, 355 }) 356 357 ch = s.AddTestingCharm(c, "storage-filesystem") 358 storageFilesystem, err := s.State.AddApplication(state.AddApplicationArgs{Name: "storage-filesystem", Charm: ch}) 359 c.Assert(err, jc.ErrorIsNil) 360 constraints, err = storageFilesystem.StorageConstraints() 361 c.Assert(err, jc.ErrorIsNil) 362 c.Assert(constraints, jc.DeepEquals, map[string]state.StorageConstraints{ 363 "data": { 364 Pool: "rootfs", 365 Count: 1, 366 Size: 1024, 367 }, 368 }) 369 } 370 371 func (s *StorageStateSuite) TestAddServiceStorageConstraintsValidation(c *gc.C) { 372 ch := s.AddTestingCharm(c, "storage-block2") 373 addService := func(storage map[string]state.StorageConstraints) (*state.Application, error) { 374 return s.State.AddApplication(state.AddApplicationArgs{Name: "storage-block2", Charm: ch, Storage: storage}) 375 } 376 assertErr := func(storage map[string]state.StorageConstraints, expect string) { 377 _, err := addService(storage) 378 c.Assert(err, gc.ErrorMatches, expect) 379 } 380 381 storageCons := map[string]state.StorageConstraints{ 382 "multi1to10": makeStorageCons("loop-pool", 1024, 1), 383 "multi2up": makeStorageCons("loop-pool", 2048, 1), 384 } 385 assertErr(storageCons, `cannot add application "storage-block2": charm "storage-block2" store "multi2up": 2 instances required, 1 specified`) 386 storageCons["multi2up"] = makeStorageCons("loop-pool", 1024, 2) 387 assertErr(storageCons, `cannot add application "storage-block2": charm "storage-block2" store "multi2up": minimum storage size is 2.0GB, 1.0GB specified`) 388 storageCons["multi2up"] = makeStorageCons("loop-pool", 2048, 2) 389 storageCons["multi1to10"] = makeStorageCons("loop-pool", 1024, 11) 390 assertErr(storageCons, `cannot add application "storage-block2": charm "storage-block2" store "multi1to10": at most 10 instances supported, 11 specified`) 391 storageCons["multi1to10"] = makeStorageCons("ebs-fast", 1024, 10) 392 assertErr(storageCons, `cannot add application "storage-block2": pool "ebs-fast" not found`) 393 storageCons["multi1to10"] = makeStorageCons("loop-pool", 1024, 10) 394 _, err := addService(storageCons) 395 c.Assert(err, jc.ErrorIsNil) 396 } 397 398 func (s *StorageStateSuite) assertAddServiceStorageConstraintsDefaults(c *gc.C, pool string, cons, expect map[string]state.StorageConstraints) { 399 if pool != "" { 400 err := s.State.UpdateModelConfig(map[string]interface{}{ 401 "storage-default-block-source": pool, 402 }, nil, nil) 403 c.Assert(err, jc.ErrorIsNil) 404 } 405 ch := s.AddTestingCharm(c, "storage-block") 406 service, err := s.State.AddApplication(state.AddApplicationArgs{Name: "storage-block2", Charm: ch, Storage: cons}) 407 c.Assert(err, jc.ErrorIsNil) 408 savedCons, err := service.StorageConstraints() 409 c.Assert(err, jc.ErrorIsNil) 410 c.Assert(savedCons, jc.DeepEquals, expect) 411 // TODO(wallyworld) - test pool name stored in data model 412 } 413 414 func (s *StorageStateSuite) TestAddServiceStorageConstraintsNoConstraintsUsed(c *gc.C) { 415 storageCons := map[string]state.StorageConstraints{ 416 "data": makeStorageCons("", 0, 0), 417 } 418 expectedCons := map[string]state.StorageConstraints{ 419 "data": makeStorageCons("loop", 1024, 1), 420 "allecto": makeStorageCons("loop", 1024, 0), 421 } 422 s.assertAddServiceStorageConstraintsDefaults(c, "loop-pool", storageCons, expectedCons) 423 } 424 425 func (s *StorageStateSuite) TestAddServiceStorageConstraintsJustCount(c *gc.C) { 426 storageCons := map[string]state.StorageConstraints{ 427 "data": makeStorageCons("", 0, 1), 428 } 429 expectedCons := map[string]state.StorageConstraints{ 430 "data": makeStorageCons("loop-pool", 1024, 1), 431 "allecto": makeStorageCons("loop", 1024, 0), 432 } 433 s.assertAddServiceStorageConstraintsDefaults(c, "loop-pool", storageCons, expectedCons) 434 } 435 436 func (s *StorageStateSuite) TestAddServiceStorageConstraintsDefaultPool(c *gc.C) { 437 storageCons := map[string]state.StorageConstraints{ 438 "data": makeStorageCons("", 2048, 1), 439 } 440 expectedCons := map[string]state.StorageConstraints{ 441 "data": makeStorageCons("loop-pool", 2048, 1), 442 "allecto": makeStorageCons("loop", 1024, 0), 443 } 444 s.assertAddServiceStorageConstraintsDefaults(c, "loop-pool", storageCons, expectedCons) 445 } 446 447 func (s *StorageStateSuite) TestAddServiceStorageConstraintsNoUserDefaultPool(c *gc.C) { 448 storageCons := map[string]state.StorageConstraints{ 449 "data": makeStorageCons("", 2048, 1), 450 } 451 expectedCons := map[string]state.StorageConstraints{ 452 "data": makeStorageCons("loop", 2048, 1), 453 "allecto": makeStorageCons("loop", 1024, 0), 454 } 455 s.assertAddServiceStorageConstraintsDefaults(c, "", storageCons, expectedCons) 456 } 457 458 func (s *StorageStateSuite) TestAddServiceStorageConstraintsDefaultSizeFallback(c *gc.C) { 459 storageCons := map[string]state.StorageConstraints{ 460 "data": makeStorageCons("loop-pool", 0, 1), 461 } 462 expectedCons := map[string]state.StorageConstraints{ 463 "data": makeStorageCons("loop-pool", 1024, 1), 464 "allecto": makeStorageCons("loop", 1024, 0), 465 } 466 s.assertAddServiceStorageConstraintsDefaults(c, "loop-pool", storageCons, expectedCons) 467 } 468 469 func (s *StorageStateSuite) TestAddServiceStorageConstraintsDefaultSizeFromCharm(c *gc.C) { 470 storageCons := map[string]state.StorageConstraints{ 471 "multi1to10": makeStorageCons("loop", 0, 3), 472 } 473 expectedCons := map[string]state.StorageConstraints{ 474 "multi1to10": makeStorageCons("loop", 1024, 3), 475 "multi2up": makeStorageCons("loop", 2048, 2), 476 } 477 ch := s.AddTestingCharm(c, "storage-block2") 478 service, err := s.State.AddApplication(state.AddApplicationArgs{Name: "storage-block2", Charm: ch, Storage: storageCons}) 479 c.Assert(err, jc.ErrorIsNil) 480 savedCons, err := service.StorageConstraints() 481 c.Assert(err, jc.ErrorIsNil) 482 c.Assert(savedCons, jc.DeepEquals, expectedCons) 483 } 484 485 func (s *StorageStateSuite) TestProviderFallbackToType(c *gc.C) { 486 ch := s.AddTestingCharm(c, "storage-block") 487 addService := func(storage map[string]state.StorageConstraints) (*state.Application, error) { 488 return s.State.AddApplication(state.AddApplicationArgs{Name: "storage-block", Charm: ch, Storage: storage}) 489 } 490 storageCons := map[string]state.StorageConstraints{ 491 "data": makeStorageCons("loop", 1024, 1), 492 } 493 _, err := addService(storageCons) 494 c.Assert(err, jc.ErrorIsNil) 495 } 496 497 func (s *StorageStateSuite) TestAddUnit(c *gc.C) { 498 s.assertStorageUnitsAdded(c) 499 } 500 501 func (s *StorageStateSuite) assertStorageUnitsAdded(c *gc.C) { 502 err := s.State.UpdateModelConfig(map[string]interface{}{ 503 "storage-default-block-source": "loop-pool", 504 }, nil, nil) 505 c.Assert(err, jc.ErrorIsNil) 506 507 // Each unit added to the service will create storage instances 508 // to satisfy the service's storage constraints. 509 ch := s.AddTestingCharm(c, "storage-block2") 510 storage := map[string]state.StorageConstraints{ 511 "multi1to10": makeStorageCons("", 1024, 1), 512 "multi2up": makeStorageCons("loop-pool", 2048, 2), 513 } 514 service := s.AddTestingServiceWithStorage(c, "storage-block2", ch, storage) 515 for i := 0; i < 2; i++ { 516 u, err := service.AddUnit() 517 c.Assert(err, jc.ErrorIsNil) 518 storageAttachments, err := s.State.UnitStorageAttachments(u.UnitTag()) 519 c.Assert(err, jc.ErrorIsNil) 520 count := make(map[string]int) 521 for _, att := range storageAttachments { 522 c.Assert(att.Unit(), gc.Equals, u.UnitTag()) 523 storageInstance, err := s.State.StorageInstance(att.StorageInstance()) 524 c.Assert(err, jc.ErrorIsNil) 525 count[storageInstance.StorageName()]++ 526 c.Assert(storageInstance.Kind(), gc.Equals, state.StorageKindBlock) 527 } 528 c.Assert(count, gc.DeepEquals, map[string]int{ 529 "multi1to10": 1, 530 "multi2up": 2, 531 }) 532 // TODO(wallyworld) - test pool name stored in data model 533 } 534 } 535 536 func (s *StorageStateSuite) TestAllStorageInstances(c *gc.C) { 537 s.assertStorageUnitsAdded(c) 538 539 all, err := s.State.AllStorageInstances() 540 c.Assert(err, jc.ErrorIsNil) 541 c.Assert(all, gc.HasLen, 6) 542 543 nameSet := set.NewStrings("multi1to10", "multi2up") 544 ownerSet := set.NewStrings("unit-storage-block2-0", "unit-storage-block2-1") 545 546 for _, one := range all { 547 c.Assert(one.Kind(), gc.DeepEquals, state.StorageKindBlock) 548 c.Assert(nameSet.Contains(one.StorageName()), jc.IsTrue) 549 c.Assert(ownerSet.Contains(one.Owner().String()), jc.IsTrue) 550 } 551 } 552 553 func (s *StorageStateSuite) TestStorageAttachments(c *gc.C) { 554 s.assertStorageUnitsAdded(c) 555 556 assertAttachments := func(tag names.StorageTag, expect ...names.UnitTag) { 557 attachments, err := s.State.StorageAttachments(tag) 558 c.Assert(err, jc.ErrorIsNil) 559 units := make([]names.UnitTag, len(attachments)) 560 for i, a := range attachments { 561 units[i] = a.Unit() 562 } 563 c.Assert(units, jc.SameContents, expect) 564 } 565 566 u0 := names.NewUnitTag("storage-block2/0") 567 u1 := names.NewUnitTag("storage-block2/1") 568 569 assertAttachments(names.NewStorageTag("multi1to10/0"), u0) 570 assertAttachments(names.NewStorageTag("multi2up/1"), u0) 571 assertAttachments(names.NewStorageTag("multi2up/2"), u0) 572 assertAttachments(names.NewStorageTag("multi1to10/3"), u1) 573 assertAttachments(names.NewStorageTag("multi2up/4"), u1) 574 assertAttachments(names.NewStorageTag("multi2up/5"), u1) 575 } 576 577 func (s *StorageStateSuite) TestAllStorageInstancesEmpty(c *gc.C) { 578 all, err := s.State.AllStorageInstances() 579 c.Assert(err, jc.ErrorIsNil) 580 c.Assert(all, gc.HasLen, 0) 581 } 582 583 func (s *StorageStateSuite) TestUnitEnsureDead(c *gc.C) { 584 _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") 585 // destroying a unit with storage attachments is fine; this is what 586 // will trigger the death and removal of storage attachments. 587 err := u.Destroy() 588 c.Assert(err, jc.ErrorIsNil) 589 // until all storage attachments are removed, the unit cannot be 590 // marked as being dead. 591 assertUnitEnsureDeadError := func() { 592 err = u.EnsureDead() 593 c.Assert(err, gc.ErrorMatches, "unit has storage attachments") 594 } 595 assertUnitEnsureDeadError() 596 err = s.State.DestroyStorageAttachment(storageTag, u.UnitTag()) 597 c.Assert(err, jc.ErrorIsNil) 598 assertUnitEnsureDeadError() 599 err = s.State.DestroyStorageInstance(storageTag) 600 c.Assert(err, jc.ErrorIsNil) 601 assertUnitEnsureDeadError() 602 err = s.State.RemoveStorageAttachment(storageTag, u.UnitTag()) 603 c.Assert(err, jc.ErrorIsNil) 604 err = u.EnsureDead() 605 c.Assert(err, jc.ErrorIsNil) 606 } 607 608 func (s *StorageStateSuite) TestRemoveStorageAttachmentsRemovesDyingInstance(c *gc.C) { 609 _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") 610 611 // Mark the storage instance as Dying, so that it will be removed 612 // when the last attachment is removed. 613 err := s.State.DestroyStorageInstance(storageTag) 614 c.Assert(err, jc.ErrorIsNil) 615 616 si, err := s.State.StorageInstance(storageTag) 617 c.Assert(err, jc.ErrorIsNil) 618 c.Assert(si.Life(), gc.Equals, state.Dying) 619 620 err = s.State.DestroyStorageAttachment(storageTag, u.UnitTag()) 621 c.Assert(err, jc.ErrorIsNil) 622 err = s.State.RemoveStorageAttachment(storageTag, u.UnitTag()) 623 c.Assert(err, jc.ErrorIsNil) 624 exists := s.storageInstanceExists(c, storageTag) 625 c.Assert(exists, jc.IsFalse) 626 } 627 628 func (s *StorageStateSuite) TestRemoveStorageAttachmentsRemovesUnitOwnedInstance(c *gc.C) { 629 _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") 630 631 // Even though the storage instance is Alive, it will be removed when 632 // the last attachment is removed, since it is not possible to add 633 // more attachments later. 634 si, err := s.State.StorageInstance(storageTag) 635 c.Assert(err, jc.ErrorIsNil) 636 c.Assert(si.Life(), gc.Equals, state.Alive) 637 638 err = s.State.DestroyStorageAttachment(storageTag, u.UnitTag()) 639 c.Assert(err, jc.ErrorIsNil) 640 err = s.State.RemoveStorageAttachment(storageTag, u.UnitTag()) 641 c.Assert(err, jc.ErrorIsNil) 642 exists := s.storageInstanceExists(c, storageTag) 643 c.Assert(exists, jc.IsFalse) 644 } 645 646 func (s *StorageStateSuite) TestConcurrentDestroyStorageInstanceRemoveStorageAttachmentsRemovesInstance(c *gc.C) { 647 _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") 648 649 defer state.SetBeforeHooks(c, s.State, func() { 650 err := s.State.DestroyStorageAttachment(storageTag, u.UnitTag()) 651 c.Assert(err, jc.ErrorIsNil) 652 err = s.State.RemoveStorageAttachment(storageTag, u.UnitTag()) 653 c.Assert(err, jc.ErrorIsNil) 654 }).Check() 655 656 // Destroying the instance should check that there are no concurrent 657 // changes to the storage instance's attachments, and recompute 658 // operations if there are. 659 err := s.State.DestroyStorageInstance(storageTag) 660 c.Assert(err, jc.ErrorIsNil) 661 662 exists := s.storageInstanceExists(c, storageTag) 663 c.Assert(exists, jc.IsFalse) 664 } 665 666 func (s *StorageStateSuite) TestConcurrentRemoveStorageAttachment(c *gc.C) { 667 _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") 668 669 err := s.State.DestroyStorageInstance(storageTag) 670 c.Assert(err, jc.ErrorIsNil) 671 672 destroy := func() { 673 err = s.State.DestroyStorageAttachment(storageTag, u.UnitTag()) 674 c.Assert(err, jc.ErrorIsNil) 675 } 676 remove := func() { 677 err = s.State.RemoveStorageAttachment(storageTag, u.UnitTag()) 678 c.Assert(err, jc.ErrorIsNil) 679 } 680 681 defer state.SetBeforeHooks(c, s.State, destroy, remove).Check() 682 destroy() 683 remove() 684 exists := s.storageInstanceExists(c, storageTag) 685 c.Assert(exists, jc.IsFalse) 686 } 687 688 func (s *StorageStateSuite) TestRemoveAliveStorageAttachmentError(c *gc.C) { 689 _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") 690 691 err := s.State.RemoveStorageAttachment(storageTag, u.UnitTag()) 692 c.Assert(err, gc.ErrorMatches, "cannot remove storage attachment data/0:storage-block/0: storage attachment is not dying") 693 694 attachments, err := s.State.UnitStorageAttachments(u.UnitTag()) 695 c.Assert(err, jc.ErrorIsNil) 696 c.Assert(attachments, gc.HasLen, 1) 697 c.Assert(attachments[0].StorageInstance(), gc.Equals, storageTag) 698 } 699 700 func (s *StorageStateSuite) TestConcurrentDestroyInstanceRemoveStorageAttachmentsRemovesInstance(c *gc.C) { 701 _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") 702 703 defer state.SetBeforeHooks(c, s.State, func() { 704 // Concurrently mark the storage instance as Dying, 705 // so that it will be removed when the last attachment 706 // is removed. 707 err := s.State.DestroyStorageInstance(storageTag) 708 c.Assert(err, jc.ErrorIsNil) 709 }, nil).Check() 710 711 // Removing the attachment should check that there are no concurrent 712 // changes to the storage instance's life, and recompute operations 713 // if it does. 714 err := s.State.DestroyStorageAttachment(storageTag, u.UnitTag()) 715 c.Assert(err, jc.ErrorIsNil) 716 err = s.State.RemoveStorageAttachment(storageTag, u.UnitTag()) 717 c.Assert(err, jc.ErrorIsNil) 718 exists := s.storageInstanceExists(c, storageTag) 719 c.Assert(exists, jc.IsFalse) 720 } 721 722 func (s *StorageStateSuite) TestConcurrentDestroyStorageInstance(c *gc.C) { 723 _, _, storageTag := s.setupSingleStorage(c, "block", "loop-pool") 724 725 defer state.SetBeforeHooks(c, s.State, func() { 726 err := s.State.DestroyStorageInstance(storageTag) 727 c.Assert(err, jc.ErrorIsNil) 728 }).Check() 729 730 err := s.State.DestroyStorageInstance(storageTag) 731 c.Assert(err, jc.ErrorIsNil) 732 733 si, err := s.State.StorageInstance(storageTag) 734 c.Assert(err, jc.ErrorIsNil) 735 c.Assert(si.Life(), gc.Equals, state.Dying) 736 } 737 738 func (s *StorageStateSuite) TestWatchStorageAttachments(c *gc.C) { 739 ch := s.AddTestingCharm(c, "storage-block2") 740 storage := map[string]state.StorageConstraints{ 741 "multi1to10": makeStorageCons("loop-pool", 1024, 1), 742 "multi2up": makeStorageCons("loop-pool", 2048, 2), 743 } 744 service := s.AddTestingServiceWithStorage(c, "storage-block2", ch, storage) 745 u, err := service.AddUnit() 746 c.Assert(err, jc.ErrorIsNil) 747 748 w := s.State.WatchStorageAttachments(u.UnitTag()) 749 defer testing.AssertStop(c, w) 750 wc := testing.NewStringsWatcherC(c, s.State, w) 751 wc.AssertChange("multi1to10/0", "multi2up/1", "multi2up/2") 752 wc.AssertNoChange() 753 754 err = s.State.DestroyStorageAttachment(names.NewStorageTag("multi2up/1"), u.UnitTag()) 755 c.Assert(err, jc.ErrorIsNil) 756 wc.AssertChange("multi2up/1") 757 wc.AssertNoChange() 758 } 759 760 func (s *StorageStateSuite) TestWatchStorageAttachment(c *gc.C) { 761 _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") 762 763 w := s.State.WatchStorageAttachment(storageTag, u.UnitTag()) 764 defer testing.AssertStop(c, w) 765 wc := testing.NewNotifyWatcherC(c, s.State, w) 766 wc.AssertOneChange() 767 768 err := s.State.DestroyStorageAttachment(storageTag, u.UnitTag()) 769 c.Assert(err, jc.ErrorIsNil) 770 wc.AssertOneChange() 771 772 err = s.State.RemoveStorageAttachment(storageTag, u.UnitTag()) 773 c.Assert(err, jc.ErrorIsNil) 774 wc.AssertOneChange() 775 } 776 777 func (s *StorageStateSuite) TestDestroyUnitStorageAttachments(c *gc.C) { 778 service := s.setupMixedScopeStorageService(c, "block") 779 u, err := service.AddUnit() 780 c.Assert(err, jc.ErrorIsNil) 781 defer state.SetBeforeHooks(c, s.State, func() { 782 err := s.State.DestroyUnitStorageAttachments(u.UnitTag()) 783 c.Assert(err, jc.ErrorIsNil) 784 attachments, err := s.State.UnitStorageAttachments(u.UnitTag()) 785 c.Assert(err, jc.ErrorIsNil) 786 c.Assert(attachments, gc.HasLen, 3) 787 for _, a := range attachments { 788 c.Assert(a.Life(), gc.Equals, state.Dying) 789 err := s.State.RemoveStorageAttachment(a.StorageInstance(), u.UnitTag()) 790 c.Assert(err, jc.ErrorIsNil) 791 } 792 }).Check() 793 794 err = s.State.DestroyUnitStorageAttachments(u.UnitTag()) 795 c.Assert(err, jc.ErrorIsNil) 796 } 797 798 func (s *StorageStateSuite) TestStorageLocationConflictIdentical(c *gc.C) { 799 s.testStorageLocationConflict( 800 c, "/srv", "/srv", 801 `cannot assign unit "storage-filesystem2/0" to machine 0: `+ 802 `validating filesystem mount points: `+ 803 `mount point "/srv" for "data-old" storage contains `+ 804 `mount point "/srv" for "data-new" storage`, 805 ) 806 } 807 808 func (s *StorageStateSuite) TestStorageLocationConflictIdenticalAfterCleaning(c *gc.C) { 809 s.testStorageLocationConflict( 810 c, "/srv", "/xyz/.././srv", 811 `cannot assign unit "storage-filesystem2/0" to machine 0: `+ 812 `validating filesystem mount points: `+ 813 `mount point "/srv" for "data-old" storage contains `+ 814 `mount point "/xyz/.././srv" for "data-new" storage`, 815 ) 816 } 817 818 func (s *StorageStateSuite) TestStorageLocationConflictSecondInsideFirst(c *gc.C) { 819 s.testStorageLocationConflict( 820 c, "/srv", "/srv/within", 821 `cannot assign unit "storage-filesystem2/0" to machine 0: `+ 822 `validating filesystem mount points: `+ 823 `mount point "/srv" for "data-old" storage contains `+ 824 `mount point "/srv/within" for "data-new" storage`, 825 ) 826 } 827 828 func (s *StorageStateSuite) TestStorageLocationConflictFirstInsideSecond(c *gc.C) { 829 s.testStorageLocationConflict( 830 c, "/srv/within", "/srv", 831 `cannot assign unit "storage-filesystem2/0" to machine 0: `+ 832 `validating filesystem mount points: `+ 833 `mount point "/srv" for "data-new" storage contains `+ 834 `mount point "/srv/within" for "data-old" storage`, 835 ) 836 } 837 838 func (s *StorageStateSuite) TestStorageLocationConflictPrefix(c *gc.C) { 839 s.testStorageLocationConflict(c, "/srv", "/srvtd", "") 840 } 841 842 func (s *StorageStateSuite) TestStorageLocationConflictSameParent(c *gc.C) { 843 s.testStorageLocationConflict(c, "/srv/1", "/srv/2", "") 844 } 845 846 func (s *StorageStateSuite) TestStorageLocationConflictAutoGenerated(c *gc.C) { 847 s.testStorageLocationConflict(c, "", "", "") 848 } 849 850 func (s *StorageStateSuite) testStorageLocationConflict(c *gc.C, first, second, expectErr string) { 851 ch1 := s.createStorageCharm(c, "storage-filesystem", charm.Storage{ 852 Name: "data-old", 853 Type: charm.StorageFilesystem, 854 CountMin: 1, 855 CountMax: 1, 856 Location: first, 857 }) 858 ch2 := s.createStorageCharm(c, "storage-filesystem2", charm.Storage{ 859 Name: "data-new", 860 Type: charm.StorageFilesystem, 861 CountMin: 1, 862 CountMax: 1, 863 Location: second, 864 }) 865 svc1 := s.AddTestingService(c, "storage-filesystem", ch1) 866 svc2 := s.AddTestingService(c, "storage-filesystem2", ch2) 867 868 u1, err := svc1.AddUnit() 869 c.Assert(err, jc.ErrorIsNil) 870 err = s.State.AssignUnit(u1, state.AssignCleanEmpty) 871 c.Assert(err, jc.ErrorIsNil) 872 873 machineId, err := u1.AssignedMachineId() 874 c.Assert(err, jc.ErrorIsNil) 875 m, err := s.State.Machine(machineId) 876 c.Assert(err, jc.ErrorIsNil) 877 878 u2, err := svc2.AddUnit() 879 c.Assert(err, jc.ErrorIsNil) 880 err = u2.AssignToMachine(m) 881 if expectErr == "" { 882 c.Assert(err, jc.ErrorIsNil) 883 } else { 884 c.Assert(err, gc.ErrorMatches, expectErr) 885 } 886 } 887 888 func mustStorageConfig(name string, provider storage.ProviderType, attrs map[string]interface{}) *storage.Config { 889 cfg, err := storage.NewConfig(name, provider, attrs) 890 if err != nil { 891 panic(err) 892 } 893 return cfg 894 } 895 896 var testingStorageProviders = storage.StaticProviderRegistry{ 897 map[storage.ProviderType]storage.Provider{ 898 "dummy": &dummystorage.StorageProvider{ 899 DefaultPools_: []*storage.Config{radiancePool}, 900 }, 901 "lancashire": &dummystorage.StorageProvider{ 902 DefaultPools_: []*storage.Config{blackPool}, 903 }, 904 }, 905 } 906 907 var radiancePool = mustStorageConfig("radiance", "dummy", map[string]interface{}{"k": "v"}) 908 var blackPool = mustStorageConfig("black", "lancashire", map[string]interface{}{}) 909 910 func (s *StorageStateSuite) TestNewModelDefaultPools(c *gc.C) { 911 st := s.Factory.MakeModel(c, &factory.ModelParams{ 912 StorageProviderRegistry: testingStorageProviders, 913 }) 914 s.AddCleanup(func(*gc.C) { st.Close() }) 915 916 // When a model is created, it is populated with the default 917 // pools of each storage provider supported by the model's 918 // cloud provider. 919 pm := poolmanager.New(state.NewStateSettings(st), testingStorageProviders) 920 listed, err := pm.List() 921 c.Assert(err, jc.ErrorIsNil) 922 sort.Sort(byStorageConfigName(listed)) 923 c.Assert(listed, jc.DeepEquals, []*storage.Config{blackPool, radiancePool}) 924 } 925 926 type byStorageConfigName []*storage.Config 927 928 func (c byStorageConfigName) Len() int { 929 return len(c) 930 } 931 932 func (c byStorageConfigName) Less(a, b int) bool { 933 return c[a].Name() < c[b].Name() 934 } 935 936 func (c byStorageConfigName) Swap(a, b int) { 937 c[a], c[b] = c[b], c[a] 938 } 939 940 // TODO(axw) the following require shared storage support to test: 941 // - StorageAttachments can't be added to Dying StorageInstance 942 // - StorageInstance without attachments is removed by Destroy 943 // - concurrent add-unit and StorageAttachment removal does not 944 // remove storage instance. 945 946 type StorageSubordinateStateSuite struct { 947 StorageStateSuiteBase 948 949 mysql *state.Application 950 mysqlUnit *state.Unit 951 mysqlRelunit *state.RelationUnit 952 subordinateApplication *state.Application 953 relation *state.Relation 954 } 955 956 var _ = gc.Suite(&StorageSubordinateStateSuite{}) 957 958 func (s *StorageSubordinateStateSuite) SetUpTest(c *gc.C) { 959 s.StorageStateSuiteBase.SetUpTest(c) 960 961 var err error 962 storageCharm := s.AddTestingCharm(c, "storage-filesystem-subordinate") 963 s.subordinateApplication = s.AddTestingService(c, "storage-filesystem-subordinate", storageCharm) 964 s.mysql = s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql")) 965 s.mysqlUnit, err = s.mysql.AddUnit() 966 c.Assert(err, jc.ErrorIsNil) 967 968 eps, err := s.State.InferEndpoints("mysql", "storage-filesystem-subordinate") 969 c.Assert(err, jc.ErrorIsNil) 970 s.relation, err = s.State.AddRelation(eps...) 971 c.Assert(err, jc.ErrorIsNil) 972 s.mysqlRelunit, err = s.relation.Unit(s.mysqlUnit) 973 c.Assert(err, jc.ErrorIsNil) 974 } 975 976 func (s *StorageSubordinateStateSuite) TestSubordinateStoragePrincipalUnassigned(c *gc.C) { 977 storageTag := names.NewStorageTag("data/0") 978 exists := s.storageInstanceExists(c, storageTag) 979 c.Assert(exists, jc.IsFalse) 980 981 err := s.mysqlRelunit.EnterScope(nil) 982 c.Assert(err, jc.ErrorIsNil) 983 984 // The subordinate unit will have been created, along with its storage. 985 exists = s.storageInstanceExists(c, storageTag) 986 c.Assert(exists, jc.IsTrue) 987 988 // The principal unit is not yet assigned to a machine, so there should 989 // be no filesystem associated with the storage instance yet. 990 _, err = s.State.StorageInstanceFilesystem(storageTag) 991 c.Assert(err, jc.Satisfies, errors.IsNotFound) 992 993 // Assigning the principal unit to a machine should cause the subordinate 994 // unit's machine storage to be created. 995 err = s.State.AssignUnit(s.mysqlUnit, state.AssignCleanEmpty) 996 c.Assert(err, jc.ErrorIsNil) 997 _ = s.storageInstanceFilesystem(c, storageTag) 998 } 999 1000 func (s *StorageSubordinateStateSuite) TestSubordinateStoragePrincipalAssigned(c *gc.C) { 1001 err := s.State.AssignUnit(s.mysqlUnit, state.AssignCleanEmpty) 1002 c.Assert(err, jc.ErrorIsNil) 1003 1004 err = s.mysqlRelunit.EnterScope(nil) 1005 c.Assert(err, jc.ErrorIsNil) 1006 1007 // The subordinate unit will have been created, along with its storage. 1008 storageTag := names.NewStorageTag("data/0") 1009 exists := s.storageInstanceExists(c, storageTag) 1010 c.Assert(exists, jc.IsTrue) 1011 1012 // The principal unit was assigned to a machine when the subordinate 1013 // unit was created, so there should be a filesystem associated with 1014 // the storage instance now. 1015 _ = s.storageInstanceFilesystem(c, storageTag) 1016 } 1017 1018 func (s *StorageSubordinateStateSuite) TestSubordinateStoragePrincipalAssignRace(c *gc.C) { 1019 // Add the subordinate before attempting to commit the transaction 1020 // that assigns the unit to a machine. The transaction should fail 1021 // and be reattempted with the knowledge of the subordinate, and 1022 // add the subordinate's storage. 1023 defer state.SetBeforeHooks(c, s.State, func() { 1024 err := s.mysqlRelunit.EnterScope(nil) 1025 c.Assert(err, jc.ErrorIsNil) 1026 }).Check() 1027 1028 err := s.State.AssignUnit(s.mysqlUnit, state.AssignCleanEmpty) 1029 c.Assert(err, jc.ErrorIsNil) 1030 _ = s.storageInstanceFilesystem(c, names.NewStorageTag("data/0")) 1031 }