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