github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/uniter/storage_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter_test 5 6 import ( 7 "errors" 8 9 "github.com/juju/names" 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 "launchpad.net/tomb" 13 14 "github.com/juju/juju/apiserver/common" 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/apiserver/uniter" 17 "github.com/juju/juju/state" 18 "github.com/juju/juju/testing" 19 ) 20 21 type storageSuite struct { 22 testing.BaseSuite 23 called []string 24 } 25 26 var _ = gc.Suite(&storageSuite{}) 27 28 func (s *storageSuite) TestWatchUnitStorageAttachments(c *gc.C) { 29 resources := common.NewResources() 30 getCanAccess := func() (common.AuthFunc, error) { 31 return func(names.Tag) bool { 32 return true 33 }, nil 34 } 35 unitTag := names.NewUnitTag("mysql/0") 36 watcher := &mockStringsWatcher{ 37 changes: make(chan []string, 1), 38 } 39 watcher.changes <- []string{"storage/0", "storage/1"} 40 state := &mockStorageState{ 41 watchStorageAttachments: func(u names.UnitTag) state.StringsWatcher { 42 c.Assert(u, gc.DeepEquals, unitTag) 43 return watcher 44 }, 45 } 46 47 storage, err := uniter.NewStorageAPI(state, resources, getCanAccess) 48 c.Assert(err, jc.ErrorIsNil) 49 watches, err := storage.WatchUnitStorageAttachments(params.Entities{ 50 Entities: []params.Entity{{unitTag.String()}}, 51 }) 52 c.Assert(err, jc.ErrorIsNil) 53 c.Assert(watches, gc.DeepEquals, params.StringsWatchResults{ 54 Results: []params.StringsWatchResult{{ 55 StringsWatcherId: "1", 56 Changes: []string{"storage/0", "storage/1"}, 57 }}, 58 }) 59 c.Assert(resources.Get("1"), gc.Equals, watcher) 60 } 61 62 func (s *storageSuite) TestWatchStorageAttachmentVolume(c *gc.C) { 63 resources := common.NewResources() 64 getCanAccess := func() (common.AuthFunc, error) { 65 return func(names.Tag) bool { 66 return true 67 }, nil 68 } 69 unitTag := names.NewUnitTag("mysql/0") 70 storageTag := names.NewStorageTag("data/0") 71 machineTag := names.NewMachineTag("66") 72 volumeTag := names.NewVolumeTag("104") 73 volume := &mockVolume{tag: volumeTag} 74 storageInstance := &mockStorageInstance{kind: state.StorageKindBlock} 75 storageWatcher := &mockNotifyWatcher{ 76 changes: make(chan struct{}, 1), 77 } 78 storageWatcher.changes <- struct{}{} 79 volumeWatcher := &mockNotifyWatcher{ 80 changes: make(chan struct{}, 1), 81 } 82 volumeWatcher.changes <- struct{}{} 83 blockDevicesWatcher := &mockNotifyWatcher{ 84 changes: make(chan struct{}, 1), 85 } 86 blockDevicesWatcher.changes <- struct{}{} 87 var calls []string 88 state := &mockStorageState{ 89 storageInstance: func(s names.StorageTag) (state.StorageInstance, error) { 90 calls = append(calls, "StorageInstance") 91 c.Assert(s, gc.DeepEquals, storageTag) 92 return storageInstance, nil 93 }, 94 storageInstanceVolume: func(s names.StorageTag) (state.Volume, error) { 95 calls = append(calls, "StorageInstanceVolume") 96 c.Assert(s, gc.DeepEquals, storageTag) 97 return volume, nil 98 }, 99 unitAssignedMachine: func(u names.UnitTag) (names.MachineTag, error) { 100 calls = append(calls, "UnitAssignedMachine") 101 c.Assert(u, gc.DeepEquals, unitTag) 102 return machineTag, nil 103 }, 104 watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) state.NotifyWatcher { 105 calls = append(calls, "WatchStorageAttachment") 106 c.Assert(s, gc.DeepEquals, storageTag) 107 c.Assert(u, gc.DeepEquals, unitTag) 108 return storageWatcher 109 }, 110 watchVolumeAttachment: func(m names.MachineTag, v names.VolumeTag) state.NotifyWatcher { 111 calls = append(calls, "WatchVolumeAttachment") 112 c.Assert(m, gc.DeepEquals, machineTag) 113 c.Assert(v, gc.DeepEquals, volumeTag) 114 return volumeWatcher 115 }, 116 watchBlockDevices: func(m names.MachineTag) state.NotifyWatcher { 117 calls = append(calls, "WatchBlockDevices") 118 c.Assert(m, gc.DeepEquals, machineTag) 119 return blockDevicesWatcher 120 }, 121 } 122 123 storage, err := uniter.NewStorageAPI(state, resources, getCanAccess) 124 c.Assert(err, jc.ErrorIsNil) 125 watches, err := storage.WatchStorageAttachments(params.StorageAttachmentIds{ 126 Ids: []params.StorageAttachmentId{{ 127 StorageTag: storageTag.String(), 128 UnitTag: unitTag.String(), 129 }}, 130 }) 131 c.Assert(err, jc.ErrorIsNil) 132 c.Assert(watches, gc.DeepEquals, params.NotifyWatchResults{ 133 Results: []params.NotifyWatchResult{{ 134 NotifyWatcherId: "1", 135 }}, 136 }) 137 c.Assert(calls, gc.DeepEquals, []string{ 138 "UnitAssignedMachine", 139 "StorageInstance", 140 "StorageInstanceVolume", 141 "WatchVolumeAttachment", 142 "WatchBlockDevices", 143 "WatchStorageAttachment", 144 }) 145 } 146 147 func (s *storageSuite) TestWatchStorageAttachmentFilesystem(c *gc.C) { 148 resources := common.NewResources() 149 getCanAccess := func() (common.AuthFunc, error) { 150 return func(names.Tag) bool { 151 return true 152 }, nil 153 } 154 unitTag := names.NewUnitTag("mysql/0") 155 storageTag := names.NewStorageTag("data/0") 156 machineTag := names.NewMachineTag("66") 157 filesystemTag := names.NewFilesystemTag("104") 158 filesystem := &mockFilesystem{tag: filesystemTag} 159 storageInstance := &mockStorageInstance{kind: state.StorageKindFilesystem} 160 storageWatcher := &mockNotifyWatcher{ 161 changes: make(chan struct{}, 1), 162 } 163 storageWatcher.changes <- struct{}{} 164 filesystemWatcher := &mockNotifyWatcher{ 165 changes: make(chan struct{}, 1), 166 } 167 filesystemWatcher.changes <- struct{}{} 168 var calls []string 169 state := &mockStorageState{ 170 storageInstance: func(s names.StorageTag) (state.StorageInstance, error) { 171 calls = append(calls, "StorageInstance") 172 c.Assert(s, gc.DeepEquals, storageTag) 173 return storageInstance, nil 174 }, 175 storageInstanceFilesystem: func(s names.StorageTag) (state.Filesystem, error) { 176 calls = append(calls, "StorageInstanceFilesystem") 177 c.Assert(s, gc.DeepEquals, storageTag) 178 return filesystem, nil 179 }, 180 unitAssignedMachine: func(u names.UnitTag) (names.MachineTag, error) { 181 calls = append(calls, "UnitAssignedMachine") 182 c.Assert(u, gc.DeepEquals, unitTag) 183 return machineTag, nil 184 }, 185 watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) state.NotifyWatcher { 186 calls = append(calls, "WatchStorageAttachment") 187 c.Assert(s, gc.DeepEquals, storageTag) 188 c.Assert(u, gc.DeepEquals, unitTag) 189 return storageWatcher 190 }, 191 watchFilesystemAttachment: func(m names.MachineTag, f names.FilesystemTag) state.NotifyWatcher { 192 calls = append(calls, "WatchFilesystemAttachment") 193 c.Assert(m, gc.DeepEquals, machineTag) 194 c.Assert(f, gc.DeepEquals, filesystemTag) 195 return filesystemWatcher 196 }, 197 } 198 199 storage, err := uniter.NewStorageAPI(state, resources, getCanAccess) 200 c.Assert(err, jc.ErrorIsNil) 201 watches, err := storage.WatchStorageAttachments(params.StorageAttachmentIds{ 202 Ids: []params.StorageAttachmentId{{ 203 StorageTag: storageTag.String(), 204 UnitTag: unitTag.String(), 205 }}, 206 }) 207 c.Assert(err, jc.ErrorIsNil) 208 c.Assert(watches, gc.DeepEquals, params.NotifyWatchResults{ 209 Results: []params.NotifyWatchResult{{ 210 NotifyWatcherId: "1", 211 }}, 212 }) 213 c.Assert(calls, gc.DeepEquals, []string{ 214 "UnitAssignedMachine", 215 "StorageInstance", 216 "StorageInstanceFilesystem", 217 "WatchFilesystemAttachment", 218 "WatchStorageAttachment", 219 }) 220 } 221 222 func (s *storageSuite) TestDestroyUnitStorageAttachments(c *gc.C) { 223 resources := common.NewResources() 224 getCanAccess := func() (common.AuthFunc, error) { 225 return func(names.Tag) bool { 226 return true 227 }, nil 228 } 229 unitTag := names.NewUnitTag("mysql/0") 230 var calls []string 231 state := &mockStorageState{ 232 destroyUnitStorageAttachments: func(u names.UnitTag) error { 233 calls = append(calls, "DestroyUnitStorageAttachments") 234 c.Assert(u, gc.DeepEquals, unitTag) 235 return nil 236 }, 237 } 238 239 storage, err := uniter.NewStorageAPI(state, resources, getCanAccess) 240 c.Assert(err, jc.ErrorIsNil) 241 errors, err := storage.DestroyUnitStorageAttachments(params.Entities{ 242 Entities: []params.Entity{{ 243 Tag: unitTag.String(), 244 }}, 245 }) 246 c.Assert(err, jc.ErrorIsNil) 247 c.Assert(calls, jc.DeepEquals, []string{"DestroyUnitStorageAttachments"}) 248 c.Assert(errors, jc.DeepEquals, params.ErrorResults{ 249 []params.ErrorResult{{}}, 250 }) 251 } 252 253 func (s *storageSuite) TestRemoveStorageAttachments(c *gc.C) { 254 setMock := func(st *mockStorageState, f func(s names.StorageTag, u names.UnitTag) error) { 255 st.remove = f 256 } 257 258 unitTag0 := names.NewUnitTag("mysql/0") 259 unitTag1 := names.NewUnitTag("mysql/1") 260 storageTag0 := names.NewStorageTag("data/0") 261 storageTag1 := names.NewStorageTag("data/1") 262 263 resources := common.NewResources() 264 getCanAccess := func() (common.AuthFunc, error) { 265 return func(tag names.Tag) bool { 266 return tag == unitTag0 267 }, nil 268 } 269 270 state := &mockStorageState{} 271 setMock(state, func(s names.StorageTag, u names.UnitTag) error { 272 c.Assert(u, gc.DeepEquals, unitTag0) 273 if s == storageTag1 { 274 return errors.New("badness") 275 } 276 return nil 277 }) 278 279 storage, err := uniter.NewStorageAPI(state, resources, getCanAccess) 280 c.Assert(err, jc.ErrorIsNil) 281 errors, err := storage.RemoveStorageAttachments(params.StorageAttachmentIds{ 282 Ids: []params.StorageAttachmentId{{ 283 StorageTag: storageTag0.String(), 284 UnitTag: unitTag0.String(), 285 }, { 286 StorageTag: storageTag1.String(), 287 UnitTag: unitTag0.String(), 288 }, { 289 StorageTag: storageTag0.String(), 290 UnitTag: unitTag1.String(), 291 }, { 292 StorageTag: unitTag0.String(), // oops 293 UnitTag: unitTag0.String(), 294 }, { 295 StorageTag: storageTag0.String(), 296 UnitTag: storageTag0.String(), // oops 297 }}, 298 }) 299 c.Assert(err, jc.ErrorIsNil) 300 c.Assert(errors, jc.DeepEquals, params.ErrorResults{ 301 Results: []params.ErrorResult{ 302 {nil}, 303 {¶ms.Error{Message: "badness"}}, 304 {¶ms.Error{Code: params.CodeUnauthorized, Message: "permission denied"}}, 305 {¶ms.Error{Message: `"unit-mysql-0" is not a valid storage tag`}}, 306 {¶ms.Error{Message: `"storage-data-0" is not a valid unit tag`}}, 307 }, 308 }) 309 } 310 311 const ( 312 unitConstraintsCall = "mockUnitStorageConstraints" 313 addStorageCall = "mockAdd" 314 ) 315 316 func (s *storageSuite) TestAddUnitStorageConstraintsErrors(c *gc.C) { 317 setMockConstraints := func(st *mockStorageState, f func(u names.UnitTag) (map[string]state.StorageConstraints, error)) { 318 st.unitStorageConstraints = f 319 } 320 321 unitTag0 := names.NewUnitTag("mysql/0") 322 storageName0 := "data" 323 storageName1 := "store" 324 325 resources := common.NewResources() 326 getCanAccess := func() (common.AuthFunc, error) { 327 return func(tag names.Tag) bool { 328 return tag == unitTag0 329 }, nil 330 } 331 332 s.called = []string{} 333 mockState := &mockStorageState{} 334 setMockConstraints(mockState, func(u names.UnitTag) (map[string]state.StorageConstraints, error) { 335 s.called = append(s.called, unitConstraintsCall) 336 c.Assert(u, gc.DeepEquals, unitTag0) 337 338 return map[string]state.StorageConstraints{ 339 storageName0: state.StorageConstraints{}, 340 }, nil 341 }) 342 343 storage, err := uniter.NewStorageAPI(mockState, resources, getCanAccess) 344 c.Assert(err, jc.ErrorIsNil) 345 size := uint64(10) 346 count := uint64(0) 347 errors, err := storage.AddUnitStorage(params.StoragesAddParams{ 348 Storages: []params.StorageAddParams{ 349 { 350 UnitTag: unitTag0.String(), 351 StorageName: storageName0, 352 Constraints: params.StorageConstraints{Pool: "matter"}, 353 }, { 354 UnitTag: unitTag0.String(), 355 StorageName: storageName0, 356 Constraints: params.StorageConstraints{Size: &size}, 357 }, { 358 UnitTag: unitTag0.String(), 359 StorageName: storageName0, 360 Constraints: params.StorageConstraints{}, 361 }, { 362 UnitTag: unitTag0.String(), 363 StorageName: storageName0, 364 Constraints: params.StorageConstraints{Count: &count}, 365 }, { 366 UnitTag: unitTag0.String(), 367 StorageName: storageName1, 368 Constraints: params.StorageConstraints{}, 369 }, 370 }}, 371 ) 372 c.Assert(err, jc.ErrorIsNil) 373 c.Assert(s.called, jc.SameContents, []string{ 374 unitConstraintsCall, 375 unitConstraintsCall, 376 unitConstraintsCall, 377 unitConstraintsCall, 378 unitConstraintsCall, 379 }) 380 c.Assert(errors, jc.DeepEquals, params.ErrorResults{ 381 Results: []params.ErrorResult{ 382 {¶ms.Error{Message: `adding storage data for unit-mysql-0: only count can be specified`}}, 383 {¶ms.Error{Message: `adding storage data for unit-mysql-0: only count can be specified`}}, 384 {¶ms.Error{Message: `adding storage data for unit-mysql-0: count must be specified`}}, 385 {¶ms.Error{Message: `adding storage data for unit-mysql-0: count must be specified`}}, 386 {¶ms.Error{ 387 Code: "not found", 388 Message: "adding storage store for unit-mysql-0: storage \"store\" not found"}}, 389 }, 390 }) 391 } 392 393 func (s *storageSuite) TestAddUnitStorage(c *gc.C) { 394 setMockConstraints := func(st *mockStorageState, f func(u names.UnitTag) (map[string]state.StorageConstraints, error)) { 395 st.unitStorageConstraints = f 396 } 397 setMockAdd := func(st *mockStorageState, f func(tag names.UnitTag, name string, cons state.StorageConstraints) error) { 398 st.addUnitStorage = f 399 } 400 401 unitTag0 := names.NewUnitTag("mysql/0") 402 storageName0 := "data" 403 storageName1 := "store" 404 405 unitPool := "real" 406 size := uint64(3) 407 unitSize := size * 2 408 unitCount := uint64(100) 409 testCount := uint64(10) 410 411 resources := common.NewResources() 412 getCanAccess := func() (common.AuthFunc, error) { 413 return func(tag names.Tag) bool { 414 return tag == unitTag0 415 }, nil 416 } 417 418 s.called = []string{} 419 mockState := &mockStorageState{} 420 421 setMockConstraints(mockState, func(u names.UnitTag) (map[string]state.StorageConstraints, error) { 422 s.called = append(s.called, unitConstraintsCall) 423 c.Assert(u, gc.DeepEquals, unitTag0) 424 425 return map[string]state.StorageConstraints{ 426 storageName0: state.StorageConstraints{ 427 Pool: unitPool, 428 Size: unitSize, 429 Count: unitCount, 430 }, 431 storageName1: state.StorageConstraints{}, 432 }, nil 433 }) 434 435 setMockAdd(mockState, func(u names.UnitTag, name string, cons state.StorageConstraints) error { 436 s.called = append(s.called, addStorageCall) 437 c.Assert(u, gc.DeepEquals, unitTag0) 438 if name == storageName1 { 439 return errors.New("badness") 440 } 441 c.Assert(cons.Count, gc.Not(gc.Equals), unitCount) 442 c.Assert(cons.Count, jc.DeepEquals, testCount) 443 c.Assert(cons.Pool, jc.DeepEquals, unitPool) 444 c.Assert(cons.Size, jc.DeepEquals, unitSize) 445 return nil 446 }) 447 448 storage, err := uniter.NewStorageAPI(mockState, resources, getCanAccess) 449 c.Assert(err, jc.ErrorIsNil) 450 errors, err := storage.AddUnitStorage(params.StoragesAddParams{ 451 Storages: []params.StorageAddParams{ 452 { 453 UnitTag: unitTag0.String(), 454 StorageName: storageName0, 455 Constraints: params.StorageConstraints{Count: &testCount}, 456 }, { 457 UnitTag: unitTag0.String(), 458 StorageName: storageName1, 459 Constraints: params.StorageConstraints{Count: &testCount}, 460 }, 461 }}, 462 ) 463 c.Assert(err, jc.ErrorIsNil) 464 c.Assert(s.called, jc.SameContents, []string{ 465 unitConstraintsCall, addStorageCall, 466 unitConstraintsCall, addStorageCall, 467 }) 468 c.Assert(errors, jc.DeepEquals, params.ErrorResults{ 469 Results: []params.ErrorResult{ 470 {nil}, 471 {¶ms.Error{Message: "adding storage store for unit-mysql-0: badness"}}, 472 }, 473 }) 474 } 475 476 type mockStorageState struct { 477 uniter.StorageStateInterface 478 destroyUnitStorageAttachments func(names.UnitTag) error 479 remove func(names.StorageTag, names.UnitTag) error 480 storageInstance func(names.StorageTag) (state.StorageInstance, error) 481 storageInstanceFilesystem func(names.StorageTag) (state.Filesystem, error) 482 storageInstanceVolume func(names.StorageTag) (state.Volume, error) 483 unitAssignedMachine func(names.UnitTag) (names.MachineTag, error) 484 watchStorageAttachments func(names.UnitTag) state.StringsWatcher 485 watchStorageAttachment func(names.StorageTag, names.UnitTag) state.NotifyWatcher 486 watchFilesystemAttachment func(names.MachineTag, names.FilesystemTag) state.NotifyWatcher 487 watchVolumeAttachment func(names.MachineTag, names.VolumeTag) state.NotifyWatcher 488 watchBlockDevices func(names.MachineTag) state.NotifyWatcher 489 addUnitStorage func(u names.UnitTag, name string, cons state.StorageConstraints) error 490 unitStorageConstraints func(u names.UnitTag) (map[string]state.StorageConstraints, error) 491 } 492 493 func (m *mockStorageState) DestroyUnitStorageAttachments(u names.UnitTag) error { 494 return m.destroyUnitStorageAttachments(u) 495 } 496 497 func (m *mockStorageState) RemoveStorageAttachment(s names.StorageTag, u names.UnitTag) error { 498 return m.remove(s, u) 499 } 500 501 func (m *mockStorageState) StorageInstance(s names.StorageTag) (state.StorageInstance, error) { 502 return m.storageInstance(s) 503 } 504 505 func (m *mockStorageState) StorageInstanceFilesystem(s names.StorageTag) (state.Filesystem, error) { 506 return m.storageInstanceFilesystem(s) 507 } 508 509 func (m *mockStorageState) StorageInstanceVolume(s names.StorageTag) (state.Volume, error) { 510 return m.storageInstanceVolume(s) 511 } 512 513 func (m *mockStorageState) UnitAssignedMachine(u names.UnitTag) (names.MachineTag, error) { 514 return m.unitAssignedMachine(u) 515 } 516 517 func (m *mockStorageState) WatchStorageAttachments(u names.UnitTag) state.StringsWatcher { 518 return m.watchStorageAttachments(u) 519 } 520 521 func (m *mockStorageState) WatchStorageAttachment(s names.StorageTag, u names.UnitTag) state.NotifyWatcher { 522 return m.watchStorageAttachment(s, u) 523 } 524 525 func (m *mockStorageState) WatchFilesystemAttachment(mtag names.MachineTag, f names.FilesystemTag) state.NotifyWatcher { 526 return m.watchFilesystemAttachment(mtag, f) 527 } 528 529 func (m *mockStorageState) WatchVolumeAttachment(mtag names.MachineTag, v names.VolumeTag) state.NotifyWatcher { 530 return m.watchVolumeAttachment(mtag, v) 531 } 532 533 func (m *mockStorageState) WatchBlockDevices(mtag names.MachineTag) state.NotifyWatcher { 534 return m.watchBlockDevices(mtag) 535 } 536 537 func (m *mockStorageState) AddStorageForUnit(tag names.UnitTag, name string, cons state.StorageConstraints) error { 538 return m.addUnitStorage(tag, name, cons) 539 } 540 541 func (m *mockStorageState) UnitStorageConstraints(u names.UnitTag) (map[string]state.StorageConstraints, error) { 542 return m.unitStorageConstraints(u) 543 } 544 545 type mockStringsWatcher struct { 546 state.StringsWatcher 547 changes chan []string 548 } 549 550 func (m *mockStringsWatcher) Changes() <-chan []string { 551 return m.changes 552 } 553 554 type mockNotifyWatcher struct { 555 tomb tomb.Tomb 556 changes chan struct{} 557 } 558 559 func (m *mockNotifyWatcher) Stop() error { 560 m.Kill() 561 return m.Wait() 562 } 563 564 func (m *mockNotifyWatcher) Kill() { 565 m.tomb.Kill(nil) 566 } 567 568 func (m *mockNotifyWatcher) Wait() error { 569 return m.tomb.Wait() 570 } 571 572 func (m *mockNotifyWatcher) Err() error { 573 return m.tomb.Err() 574 } 575 576 func (m *mockNotifyWatcher) Changes() <-chan struct{} { 577 return m.changes 578 } 579 580 type mockVolume struct { 581 state.Volume 582 tag names.VolumeTag 583 } 584 585 func (m *mockVolume) VolumeTag() names.VolumeTag { 586 return m.tag 587 } 588 589 type mockFilesystem struct { 590 state.Filesystem 591 tag names.FilesystemTag 592 } 593 594 func (m *mockFilesystem) FilesystemTag() names.FilesystemTag { 595 return m.tag 596 } 597 598 type mockStorageInstance struct { 599 state.StorageInstance 600 kind state.StorageKind 601 } 602 603 func (m *mockStorageInstance) Kind() state.StorageKind { 604 return m.kind 605 }