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