github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/storage/attachments_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storage_test 5 6 import ( 7 "io/ioutil" 8 "path/filepath" 9 10 "github.com/juju/names" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 "gopkg.in/juju/charm.v5/hooks" 14 15 "github.com/juju/juju/api/watcher" 16 "github.com/juju/juju/apiserver/params" 17 corestorage "github.com/juju/juju/storage" 18 "github.com/juju/juju/testing" 19 "github.com/juju/juju/worker/uniter/hook" 20 "github.com/juju/juju/worker/uniter/storage" 21 ) 22 23 type attachmentsSuite struct { 24 testing.BaseSuite 25 } 26 27 var _ = gc.Suite(&attachmentsSuite{}) 28 29 func assertStorageTags(c *gc.C, a *storage.Attachments, tags ...names.StorageTag) { 30 c.Assert(a.StorageTags(), jc.SameContents, tags) 31 } 32 33 func (s *attachmentsSuite) TestNewAttachments(c *gc.C) { 34 stateDir := filepath.Join(c.MkDir(), "nonexistent") 35 unitTag := names.NewUnitTag("mysql/0") 36 abort := make(chan struct{}) 37 st := &mockStorageAccessor{ 38 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 39 c.Assert(u, gc.Equals, unitTag) 40 return nil, nil 41 }, 42 } 43 44 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 45 c.Assert(err, jc.ErrorIsNil) 46 defer func() { 47 err := att.Stop() 48 c.Assert(err, jc.ErrorIsNil) 49 }() 50 // state dir should have been created. 51 c.Assert(stateDir, jc.IsDirectory) 52 } 53 54 func (s *attachmentsSuite) TestNewAttachmentsInit(c *gc.C) { 55 stateDir := c.MkDir() 56 unitTag := names.NewUnitTag("mysql/0") 57 abort := make(chan struct{}) 58 59 // Simulate remote state returning a single Alive storage attachment. 60 attachmentIds := []params.StorageAttachmentId{{ 61 StorageTag: "storage-data-0", 62 UnitTag: unitTag.String(), 63 }} 64 st := &mockStorageAccessor{ 65 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 66 c.Assert(u, gc.Equals, unitTag) 67 return attachmentIds, nil 68 }, 69 watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) { 70 return newMockNotifyWatcher(), nil 71 }, 72 } 73 74 storageTag := names.NewStorageTag("data/0") 75 withAttachments := func(f func(*storage.Attachments)) { 76 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 77 c.Assert(err, jc.ErrorIsNil) 78 defer func() { 79 err := att.Stop() 80 c.Assert(err, jc.ErrorIsNil) 81 }() 82 f(att) 83 } 84 85 // No state files, so no storagers will be started. 86 var called int 87 withAttachments(func(att *storage.Attachments) { 88 called++ 89 c.Assert(att.Pending(), gc.Equals, 1) 90 err := att.ValidateHook(hook.Info{ 91 Kind: hooks.StorageAttached, 92 StorageId: storageTag.Id(), 93 }) 94 c.Assert(err, gc.ErrorMatches, `unknown storage "data/0"`) 95 assertStorageTags(c, att) // no active attachment 96 }) 97 c.Assert(called, gc.Equals, 1) 98 99 // Commit a storage-attached to local state and try again. 100 state0, err := storage.ReadStateFile(stateDir, storageTag) 101 c.Assert(err, jc.ErrorIsNil) 102 err = state0.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/0"}) 103 c.Assert(err, jc.ErrorIsNil) 104 // Create an extra one so we can make sure it gets removed. 105 state1, err := storage.ReadStateFile(stateDir, names.NewStorageTag("data/1")) 106 c.Assert(err, jc.ErrorIsNil) 107 err = state1.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/1"}) 108 c.Assert(err, jc.ErrorIsNil) 109 110 withAttachments(func(att *storage.Attachments) { 111 called++ 112 c.Assert(att.Pending(), gc.Equals, 0) 113 err := att.ValidateHook(hook.Info{ 114 Kind: hooks.StorageDetaching, 115 StorageId: storageTag.Id(), 116 }) 117 c.Assert(err, jc.ErrorIsNil) 118 err = att.ValidateHook(hook.Info{ 119 Kind: hooks.StorageAttached, 120 StorageId: "data/1", 121 }) 122 c.Assert(err, gc.ErrorMatches, `unknown storage "data/1"`) 123 assertStorageTags(c, att, storageTag) 124 }) 125 c.Assert(called, gc.Equals, 2) 126 c.Assert(filepath.Join(stateDir, "data-0"), jc.IsNonEmptyFile) 127 c.Assert(filepath.Join(stateDir, "data-1"), jc.DoesNotExist) 128 } 129 130 func (s *attachmentsSuite) TestAttachmentsUpdateShortCircuitDeath(c *gc.C) { 131 stateDir := c.MkDir() 132 unitTag := names.NewUnitTag("mysql/0") 133 abort := make(chan struct{}) 134 135 var removed bool 136 storageTag := names.NewStorageTag("data/0") 137 st := &mockStorageAccessor{ 138 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 139 c.Assert(u, gc.Equals, unitTag) 140 return nil, nil 141 }, 142 watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) { 143 w := newMockNotifyWatcher() 144 return w, nil 145 }, 146 storageAttachmentLife: func(ids []params.StorageAttachmentId) ([]params.LifeResult, error) { 147 return []params.LifeResult{{Life: params.Dying}}, nil 148 }, 149 remove: func(s names.StorageTag, u names.UnitTag) error { 150 removed = true 151 c.Assert(s, gc.Equals, storageTag) 152 c.Assert(u, gc.Equals, unitTag) 153 return nil 154 }, 155 } 156 157 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 158 c.Assert(err, jc.ErrorIsNil) 159 defer func() { 160 err := att.Stop() 161 c.Assert(err, jc.ErrorIsNil) 162 }() 163 err = att.UpdateStorage([]names.StorageTag{storageTag}) 164 c.Assert(err, jc.ErrorIsNil) 165 c.Assert(removed, jc.IsTrue) 166 } 167 168 func (s *attachmentsSuite) TestAttachmentsStorage(c *gc.C) { 169 stateDir := c.MkDir() 170 unitTag := names.NewUnitTag("mysql/0") 171 abort := make(chan struct{}) 172 173 storageTag := names.NewStorageTag("data/0") 174 attachment := params.StorageAttachment{ 175 StorageTag: storageTag.String(), 176 UnitTag: unitTag.String(), 177 Life: params.Alive, 178 Kind: params.StorageKindBlock, 179 Location: "/dev/sdb", 180 } 181 182 st := &mockStorageAccessor{ 183 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 184 c.Assert(u, gc.Equals, unitTag) 185 return nil, nil 186 }, 187 watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) { 188 w := newMockNotifyWatcher() 189 w.changes <- struct{}{} 190 return w, nil 191 }, 192 storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) { 193 c.Assert(s, gc.Equals, storageTag) 194 return attachment, nil 195 }, 196 } 197 198 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 199 c.Assert(err, jc.ErrorIsNil) 200 defer func() { 201 err := att.Stop() 202 c.Assert(err, jc.ErrorIsNil) 203 }() 204 205 // There should be no context for data/0 until a hook is queued. 206 _, ok := att.Storage(storageTag) 207 c.Assert(ok, jc.IsFalse) 208 assertStorageTags(c, att) 209 210 err = att.UpdateStorage([]names.StorageTag{storageTag}) 211 c.Assert(err, jc.ErrorIsNil) 212 hi := waitOneHook(c, att.Hooks()) 213 c.Assert(hi, gc.Equals, hook.Info{ 214 Kind: hooks.StorageAttached, 215 StorageId: storageTag.Id(), 216 }) 217 assertStorageTags(c, att, storageTag) 218 219 ctx, ok := att.Storage(storageTag) 220 c.Assert(ok, jc.IsTrue) 221 c.Assert(ctx, gc.NotNil) 222 c.Assert(ctx.Tag(), gc.Equals, storageTag) 223 c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindBlock) 224 c.Assert(ctx.Location(), gc.Equals, "/dev/sdb") 225 } 226 227 func (s *attachmentsSuite) TestAttachmentsCommitHook(c *gc.C) { 228 stateDir := c.MkDir() 229 unitTag := names.NewUnitTag("mysql/0") 230 abort := make(chan struct{}) 231 232 var removed bool 233 storageTag := names.NewStorageTag("data/0") 234 attachment := params.StorageAttachment{ 235 StorageTag: storageTag.String(), 236 UnitTag: unitTag.String(), 237 Life: params.Alive, 238 Kind: params.StorageKindBlock, 239 Location: "/dev/sdb", 240 } 241 st := &mockStorageAccessor{ 242 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 243 c.Assert(u, gc.Equals, unitTag) 244 return nil, nil 245 }, 246 watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) { 247 w := newMockNotifyWatcher() 248 w.changes <- struct{}{} 249 return w, nil 250 }, 251 storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) { 252 c.Assert(s, gc.Equals, storageTag) 253 return attachment, nil 254 }, 255 remove: func(s names.StorageTag, u names.UnitTag) error { 256 removed = true 257 c.Assert(s, gc.Equals, storageTag) 258 return nil 259 }, 260 } 261 262 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 263 c.Assert(err, jc.ErrorIsNil) 264 defer func() { 265 err := att.Stop() 266 c.Assert(err, jc.ErrorIsNil) 267 }() 268 err = att.UpdateStorage([]names.StorageTag{storageTag}) 269 c.Assert(err, jc.ErrorIsNil) 270 c.Assert(att.Pending(), gc.Equals, 1) 271 272 stateFile := filepath.Join(stateDir, "data-0") 273 c.Assert(stateFile, jc.DoesNotExist) 274 275 err = att.CommitHook(hook.Info{ 276 Kind: hooks.StorageAttached, 277 StorageId: storageTag.Id(), 278 }) 279 c.Assert(err, jc.ErrorIsNil) 280 data, err := ioutil.ReadFile(stateFile) 281 c.Assert(err, jc.ErrorIsNil) 282 c.Assert(string(data), gc.Equals, "attached: true\n") 283 c.Assert(att.Pending(), gc.Equals, 0) 284 285 c.Assert(removed, jc.IsFalse) 286 err = att.CommitHook(hook.Info{ 287 Kind: hooks.StorageDetaching, 288 StorageId: storageTag.Id(), 289 }) 290 c.Assert(err, jc.ErrorIsNil) 291 c.Assert(stateFile, jc.DoesNotExist) 292 c.Assert(removed, jc.IsTrue) 293 } 294 295 func (s *attachmentsSuite) TestAttachmentsSetDying(c *gc.C) { 296 stateDir := c.MkDir() 297 unitTag := names.NewUnitTag("mysql/0") 298 storageTag0 := names.NewStorageTag("data/0") 299 storageTag1 := names.NewStorageTag("data/1") 300 abort := make(chan struct{}) 301 302 var destroyed, removed bool 303 st := &mockStorageAccessor{ 304 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 305 c.Assert(u, gc.Equals, unitTag) 306 return []params.StorageAttachmentId{{ 307 StorageTag: storageTag0.String(), 308 UnitTag: unitTag.String(), 309 }, { 310 StorageTag: storageTag1.String(), 311 UnitTag: unitTag.String(), 312 }}, nil 313 }, 314 watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) { 315 w := newMockNotifyWatcher() 316 w.changes <- struct{}{} 317 return w, nil 318 }, 319 storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) { 320 c.Assert(u, gc.Equals, unitTag) 321 if s == storageTag0 { 322 return params.StorageAttachment{}, ¶ms.Error{ 323 Message: "not provisioned", 324 Code: params.CodeNotProvisioned, 325 } 326 } 327 c.Assert(s, gc.Equals, storageTag1) 328 return params.StorageAttachment{ 329 StorageTag: storageTag1.String(), 330 UnitTag: unitTag.String(), 331 Life: params.Dying, 332 Kind: params.StorageKindBlock, 333 Location: "/dev/sdb", 334 }, nil 335 }, 336 storageAttachmentLife: func(ids []params.StorageAttachmentId) ([]params.LifeResult, error) { 337 results := make([]params.LifeResult, len(ids)) 338 for i := range ids { 339 results[i].Life = params.Dying 340 } 341 return results, nil 342 }, 343 destroyUnitStorageAttachments: func(u names.UnitTag) error { 344 c.Assert(u, gc.Equals, unitTag) 345 destroyed = true 346 return nil 347 }, 348 remove: func(s names.StorageTag, u names.UnitTag) error { 349 c.Assert(removed, jc.IsFalse) 350 c.Assert(s, gc.Equals, storageTag0) 351 c.Assert(u, gc.Equals, unitTag) 352 removed = true 353 return nil 354 }, 355 } 356 357 state1, err := storage.ReadStateFile(stateDir, storageTag1) 358 c.Assert(err, jc.ErrorIsNil) 359 err = state1.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: storageTag1.Id()}) 360 c.Assert(err, jc.ErrorIsNil) 361 362 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 363 c.Assert(err, jc.ErrorIsNil) 364 defer func() { 365 err := att.Stop() 366 c.Assert(err, jc.ErrorIsNil) 367 }() 368 c.Assert(att.Pending(), gc.Equals, 1) 369 370 err = att.SetDying() 371 c.Assert(err, jc.ErrorIsNil) 372 c.Assert(att.Pending(), gc.Equals, 0) 373 c.Assert(destroyed, jc.IsTrue) 374 c.Assert(removed, jc.IsTrue) 375 } 376 377 type attachmentsUpdateSuite struct { 378 testing.BaseSuite 379 unitTag names.UnitTag 380 storageTag0 names.StorageTag 381 storageTag1 names.StorageTag 382 attachmentsByTag map[names.StorageTag]*params.StorageAttachment 383 unitAttachmentIds map[names.UnitTag][]params.StorageAttachmentId 384 att *storage.Attachments 385 } 386 387 var _ = gc.Suite(&attachmentsUpdateSuite{}) 388 389 func (s *attachmentsUpdateSuite) SetUpTest(c *gc.C) { 390 s.BaseSuite.SetUpTest(c) 391 s.unitTag = names.NewUnitTag("mysql/0") 392 s.storageTag0 = names.NewStorageTag("data/0") 393 s.storageTag1 = names.NewStorageTag("data/1") 394 s.attachmentsByTag = map[names.StorageTag]*params.StorageAttachment{ 395 s.storageTag0: { 396 StorageTag: s.storageTag0.String(), 397 UnitTag: s.unitTag.String(), 398 Life: params.Alive, 399 Kind: params.StorageKindBlock, 400 Location: "/dev/sdb", 401 }, 402 s.storageTag1: { 403 StorageTag: s.storageTag1.String(), 404 UnitTag: s.unitTag.String(), 405 Life: params.Dying, 406 Kind: params.StorageKindBlock, 407 Location: "/dev/sdb", 408 }, 409 } 410 s.unitAttachmentIds = nil 411 412 st := &mockStorageAccessor{ 413 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 414 c.Assert(u, gc.Equals, s.unitTag) 415 return s.unitAttachmentIds[u], nil 416 }, 417 watchStorageAttachment: func(storageTag names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) { 418 w := newMockNotifyWatcher() 419 w.changes <- struct{}{} 420 return w, nil 421 }, 422 storageAttachment: func(storageTag names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) { 423 att, ok := s.attachmentsByTag[storageTag] 424 c.Assert(ok, jc.IsTrue) 425 return *att, nil 426 }, 427 remove: func(storageTag names.StorageTag, u names.UnitTag) error { 428 c.Assert(storageTag, gc.Equals, s.storageTag1) 429 return nil 430 }, 431 } 432 433 stateDir := c.MkDir() 434 abort := make(chan struct{}) 435 var err error 436 s.att, err = storage.NewAttachments(st, s.unitTag, stateDir, abort) 437 c.Assert(err, jc.ErrorIsNil) 438 s.AddCleanup(func(c *gc.C) { 439 err := s.att.Stop() 440 c.Assert(err, jc.ErrorIsNil) 441 }) 442 } 443 444 func (s *attachmentsUpdateSuite) TestAttachmentsUpdateUntrackedAlive(c *gc.C) { 445 // data/0 is initially unattached and untracked, so 446 // updating with Alive will cause a storager to be 447 // started and a storage-attached event to be emitted. 448 assertStorageTags(c, s.att) 449 for i := 0; i < 2; i++ { 450 // Updating twice, to ensure idempotency. 451 err := s.att.UpdateStorage([]names.StorageTag{s.storageTag0}) 452 c.Assert(err, jc.ErrorIsNil) 453 } 454 assertStorageTags(c, s.att, s.storageTag0) 455 hi := waitOneHook(c, s.att.Hooks()) 456 c.Assert(hi, gc.Equals, hook.Info{ 457 Kind: hooks.StorageAttached, 458 StorageId: s.storageTag0.Id(), 459 }) 460 c.Assert(s.att.Pending(), gc.Equals, 1) 461 } 462 463 func (s *attachmentsUpdateSuite) TestAttachmentsUpdateUntrackedDying(c *gc.C) { 464 // data/1 is initially unattached and untracked, so 465 // updating with Dying will not cause a storager to 466 // be started. 467 err := s.att.UpdateStorage([]names.StorageTag{s.storageTag1}) 468 c.Assert(err, jc.ErrorIsNil) 469 assertNoHooks(c, s.att.Hooks()) 470 c.Assert(s.att.Pending(), gc.Equals, 0) 471 assertStorageTags(c, s.att) 472 } 473 474 func (s *attachmentsUpdateSuite) TestAttachmentsRefresh(c *gc.C) { 475 // This test combines the above two. 476 s.unitAttachmentIds = map[names.UnitTag][]params.StorageAttachmentId{ 477 s.unitTag: []params.StorageAttachmentId{{ 478 StorageTag: s.storageTag0.String(), 479 UnitTag: s.unitTag.String(), 480 }, { 481 StorageTag: s.storageTag1.String(), 482 UnitTag: s.unitTag.String(), 483 }}, 484 } 485 for i := 0; i < 2; i++ { 486 // Refresh twice, to ensure idempotency. 487 err := s.att.Refresh() 488 c.Assert(err, jc.ErrorIsNil) 489 } 490 hi := waitOneHook(c, s.att.Hooks()) 491 c.Assert(hi, gc.Equals, hook.Info{ 492 Kind: hooks.StorageAttached, 493 StorageId: s.storageTag0.Id(), 494 }) 495 c.Assert(s.att.Pending(), gc.Equals, 1) 496 } 497 498 func (s *attachmentsUpdateSuite) TestAttachmentsUpdateShortCircuitNoHooks(c *gc.C) { 499 // Cause an Alive hook to be queued, but don't consume it; 500 // then update to Dying, and ensure no hooks are generated. 501 // Additionally, the storager should be stopped and no 502 // longer tracked. 503 s.attachmentsByTag[s.storageTag1].Life = params.Alive 504 err := s.att.UpdateStorage([]names.StorageTag{s.storageTag1}) 505 c.Assert(err, jc.ErrorIsNil) 506 err = s.att.ValidateHook(hook.Info{ 507 Kind: hooks.StorageAttached, 508 StorageId: s.storageTag1.Id(), 509 }) 510 c.Assert(err, jc.ErrorIsNil) 511 c.Assert(s.att.Pending(), gc.Equals, 1) 512 513 s.attachmentsByTag[s.storageTag1].Life = params.Dying 514 err = s.att.UpdateStorage([]names.StorageTag{s.storageTag1}) 515 c.Assert(err, jc.ErrorIsNil) 516 assertNoHooks(c, s.att.Hooks()) 517 err = s.att.ValidateHook(hook.Info{ 518 Kind: hooks.StorageAttached, 519 StorageId: s.storageTag1.Id(), 520 }) 521 c.Assert(err, gc.ErrorMatches, `unknown storage "data/1"`) 522 c.Assert(s.att.Pending(), gc.Equals, 0) 523 assertStorageTags(c, s.att) 524 }