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