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