github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 "gopkg.in/juju/charm.v6/hooks" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/core/model" 18 corestorage "github.com/juju/juju/storage" 19 "github.com/juju/juju/testing" 20 "github.com/juju/juju/worker/uniter/hook" 21 "github.com/juju/juju/worker/uniter/operation" 22 "github.com/juju/juju/worker/uniter/remotestate" 23 "github.com/juju/juju/worker/uniter/resolver" 24 "github.com/juju/juju/worker/uniter/storage" 25 ) 26 27 type attachmentsSuite struct { 28 testing.BaseSuite 29 30 modelType model.ModelType 31 } 32 33 type caasAttachmentsSuite struct { 34 attachmentsSuite 35 } 36 37 type iaasAttachmentsSuite struct { 38 attachmentsSuite 39 } 40 41 var _ = gc.Suite(&caasAttachmentsSuite{}) 42 var _ = gc.Suite(&iaasAttachmentsSuite{}) 43 44 func (s *caasAttachmentsSuite) SetUpTest(c *gc.C) { 45 s.modelType = model.CAAS 46 s.attachmentsSuite.SetUpTest(c) 47 } 48 49 func (s *iaasAttachmentsSuite) SetUpTest(c *gc.C) { 50 s.modelType = model.IAAS 51 s.attachmentsSuite.SetUpTest(c) 52 } 53 54 func assertStorageTags(c *gc.C, a *storage.Attachments, tags ...names.StorageTag) { 55 sTags, err := a.StorageTags() 56 c.Assert(err, jc.ErrorIsNil) 57 c.Assert(sTags, jc.SameContents, tags) 58 } 59 60 func (s *attachmentsSuite) TestNewAttachments(c *gc.C) { 61 stateDir := filepath.Join(c.MkDir(), "nonexistent") 62 unitTag := names.NewUnitTag("mysql/0") 63 abort := make(chan struct{}) 64 st := &mockStorageAccessor{ 65 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 66 c.Assert(u, gc.Equals, unitTag) 67 return nil, nil 68 }, 69 } 70 71 _, err := storage.NewAttachments(st, unitTag, stateDir, abort) 72 c.Assert(err, jc.ErrorIsNil) 73 // state dir should have been created. 74 c.Assert(stateDir, jc.IsDirectory) 75 } 76 77 func (s *attachmentsSuite) TestNewAttachmentsInit(c *gc.C) { 78 stateDir := c.MkDir() 79 unitTag := names.NewUnitTag("mysql/0") 80 abort := make(chan struct{}) 81 82 // Simulate remote state returning a single Alive storage attachment. 83 storageTag := names.NewStorageTag("data/0") 84 attachmentIds := []params.StorageAttachmentId{{ 85 StorageTag: storageTag.String(), 86 UnitTag: unitTag.String(), 87 }} 88 attachment := params.StorageAttachment{ 89 StorageTag: storageTag.String(), 90 UnitTag: unitTag.String(), 91 Life: params.Alive, 92 Kind: params.StorageKindBlock, 93 Location: "/dev/sdb", 94 } 95 96 st := &mockStorageAccessor{ 97 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 98 c.Assert(u, gc.Equals, unitTag) 99 return attachmentIds, nil 100 }, 101 storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) { 102 c.Assert(s, gc.Equals, storageTag) 103 return attachment, nil 104 }, 105 } 106 107 withAttachments := func(f func(*storage.Attachments)) { 108 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 109 c.Assert(err, jc.ErrorIsNil) 110 f(att) 111 } 112 113 // No state files, so no storagers will be started. 114 var called int 115 withAttachments(func(att *storage.Attachments) { 116 called++ 117 c.Assert(att.Pending(), gc.Equals, 1) 118 err := att.ValidateHook(hook.Info{ 119 Kind: hooks.StorageAttached, 120 StorageId: storageTag.Id(), 121 }) 122 c.Assert(err, gc.ErrorMatches, `unknown storage "data/0"`) 123 assertStorageTags(c, att) // no active attachment 124 }) 125 c.Assert(called, gc.Equals, 1) 126 127 // Commit a storage-attached to local state and try again. 128 state0, err := storage.ReadStateFile(stateDir, storageTag) 129 c.Assert(err, jc.ErrorIsNil) 130 err = state0.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/0"}) 131 c.Assert(err, jc.ErrorIsNil) 132 // Create an extra one so we can make sure it gets removed. 133 state1, err := storage.ReadStateFile(stateDir, names.NewStorageTag("data/1")) 134 c.Assert(err, jc.ErrorIsNil) 135 err = state1.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/1"}) 136 c.Assert(err, jc.ErrorIsNil) 137 138 withAttachments(func(att *storage.Attachments) { 139 // We should be able to get the initial storage context 140 // for existing storage immediately, without having to 141 // wait for any hooks to fire. 142 ctx, err := att.Storage(storageTag) 143 c.Assert(err, jc.ErrorIsNil) 144 c.Assert(ctx, gc.NotNil) 145 c.Assert(ctx.Tag(), gc.Equals, storageTag) 146 c.Assert(ctx.Tag(), gc.Equals, storageTag) 147 c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindBlock) 148 c.Assert(ctx.Location(), gc.Equals, "/dev/sdb") 149 150 called++ 151 c.Assert(att.Pending(), gc.Equals, 0) 152 err = att.ValidateHook(hook.Info{ 153 Kind: hooks.StorageDetaching, 154 StorageId: storageTag.Id(), 155 }) 156 c.Assert(err, jc.ErrorIsNil) 157 err = att.ValidateHook(hook.Info{ 158 Kind: hooks.StorageAttached, 159 StorageId: "data/1", 160 }) 161 c.Assert(err, gc.ErrorMatches, `unknown storage "data/1"`) 162 assertStorageTags(c, att, storageTag) 163 }) 164 c.Assert(called, gc.Equals, 2) 165 c.Assert(filepath.Join(stateDir, "data-0"), jc.IsNonEmptyFile) 166 c.Assert(filepath.Join(stateDir, "data-1"), jc.DoesNotExist) 167 } 168 169 func (s *attachmentsSuite) TestAttachmentsUpdateShortCircuitDeath(c *gc.C) { 170 stateDir := c.MkDir() 171 unitTag := names.NewUnitTag("mysql/0") 172 abort := make(chan struct{}) 173 174 storageTag0 := names.NewStorageTag("data/0") 175 storageTag1 := names.NewStorageTag("data/1") 176 177 removed := names.NewSet() 178 st := &mockStorageAccessor{ 179 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 180 return nil, nil 181 }, 182 remove: func(s names.StorageTag, u names.UnitTag) error { 183 c.Assert(u, gc.Equals, unitTag) 184 removed.Add(s) 185 return nil 186 }, 187 } 188 189 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 190 c.Assert(err, jc.ErrorIsNil) 191 r := storage.NewResolver(att, s.modelType) 192 193 // First make sure we create a storage-attached hook operation for 194 // data/0. We do this to show that until the hook is *committed*, 195 // we will still short-circuit removal. 196 localState := resolver.LocalState{State: operation.State{ 197 Kind: operation.Continue, 198 }} 199 _, err = r.NextOp(localState, remotestate.Snapshot{ 200 Life: params.Alive, 201 Storage: map[names.StorageTag]remotestate.StorageSnapshot{ 202 storageTag0: { 203 Life: params.Alive, 204 Kind: params.StorageKindBlock, 205 Location: "/dev/sdb", 206 Attached: true, 207 }, 208 }, 209 }, &mockOperations{}) 210 c.Assert(err, jc.ErrorIsNil) 211 212 for _, storageTag := range []names.StorageTag{storageTag0, storageTag1} { 213 _, err = r.NextOp(localState, remotestate.Snapshot{ 214 Life: params.Alive, 215 Storage: map[names.StorageTag]remotestate.StorageSnapshot{ 216 storageTag: {Life: params.Dying}, 217 }, 218 }, nil) 219 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 220 } 221 c.Assert(removed.SortedValues(), jc.DeepEquals, []names.Tag{ 222 storageTag0, storageTag1, 223 }) 224 } 225 226 func (s *attachmentsSuite) TestAttachmentsStorage(c *gc.C) { 227 stateDir := c.MkDir() 228 unitTag := names.NewUnitTag("mysql/0") 229 abort := make(chan struct{}) 230 231 st := &mockStorageAccessor{ 232 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 233 return nil, nil 234 }, 235 } 236 237 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 238 c.Assert(err, jc.ErrorIsNil) 239 r := storage.NewResolver(att, s.modelType) 240 241 storageTag := names.NewStorageTag("data/0") 242 _, err = att.Storage(storageTag) 243 c.Assert(err, jc.Satisfies, errors.IsNotFound) 244 assertStorageTags(c, att) 245 246 // Inform the resolver of an attachment. 247 localState := resolver.LocalState{State: operation.State{ 248 Kind: operation.Continue, 249 }} 250 op, err := r.NextOp(localState, remotestate.Snapshot{ 251 Life: params.Alive, 252 Storage: map[names.StorageTag]remotestate.StorageSnapshot{ 253 storageTag: { 254 Kind: params.StorageKindBlock, 255 Life: params.Alive, 256 Location: "/dev/sdb", 257 Attached: true, 258 }, 259 }, 260 }, &mockOperations{}) 261 c.Assert(err, jc.ErrorIsNil) 262 c.Assert(op.String(), gc.Equals, "run hook storage-attached") 263 assertStorageTags(c, att, storageTag) 264 265 ctx, err := att.Storage(storageTag) 266 c.Assert(err, jc.ErrorIsNil) 267 c.Assert(ctx, gc.NotNil) 268 c.Assert(ctx.Tag(), gc.Equals, storageTag) 269 c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindBlock) 270 c.Assert(ctx.Location(), gc.Equals, "/dev/sdb") 271 } 272 273 func (s *attachmentsSuite) TestAttachmentsCommitHook(c *gc.C) { 274 stateDir := c.MkDir() 275 unitTag := names.NewUnitTag("mysql/0") 276 abort := make(chan struct{}) 277 278 var removed bool 279 storageTag := names.NewStorageTag("data/0") 280 st := &mockStorageAccessor{ 281 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 282 return nil, nil 283 }, 284 remove: func(s names.StorageTag, u names.UnitTag) error { 285 removed = true 286 c.Assert(s, gc.Equals, storageTag) 287 return nil 288 }, 289 } 290 291 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 292 c.Assert(err, jc.ErrorIsNil) 293 r := storage.NewResolver(att, s.modelType) 294 295 // Inform the resolver of an attachment. 296 localState := resolver.LocalState{State: operation.State{ 297 Kind: operation.Continue, 298 }} 299 _, err = r.NextOp(localState, remotestate.Snapshot{ 300 Life: params.Alive, 301 Storage: map[names.StorageTag]remotestate.StorageSnapshot{ 302 storageTag: { 303 Kind: params.StorageKindBlock, 304 Life: params.Alive, 305 Location: "/dev/sdb", 306 Attached: true, 307 }, 308 }, 309 }, &mockOperations{}) 310 c.Assert(err, jc.ErrorIsNil) 311 c.Assert(att.Pending(), gc.Equals, 1) 312 313 // No file exists until storage-attached is committed. 314 stateFile := filepath.Join(stateDir, "data-0") 315 c.Assert(stateFile, jc.DoesNotExist) 316 317 err = att.CommitHook(hook.Info{ 318 Kind: hooks.StorageAttached, 319 StorageId: storageTag.Id(), 320 }) 321 c.Assert(err, jc.ErrorIsNil) 322 data, err := ioutil.ReadFile(stateFile) 323 c.Assert(err, jc.ErrorIsNil) 324 c.Assert(string(data), gc.Equals, "attached: true\n") 325 c.Assert(att.Pending(), gc.Equals, 0) 326 327 c.Assert(removed, jc.IsFalse) 328 err = att.CommitHook(hook.Info{ 329 Kind: hooks.StorageDetaching, 330 StorageId: storageTag.Id(), 331 }) 332 c.Assert(err, jc.ErrorIsNil) 333 c.Assert(stateFile, jc.DoesNotExist) 334 c.Assert(removed, jc.IsTrue) 335 } 336 337 func (s *attachmentsSuite) TestAttachmentsSetDying(c *gc.C) { 338 stateDir := c.MkDir() 339 unitTag := names.NewUnitTag("mysql/0") 340 abort := make(chan struct{}) 341 342 storageTag := names.NewStorageTag("data/0") 343 var destroyed, removed bool 344 st := &mockStorageAccessor{ 345 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 346 c.Assert(u, gc.Equals, unitTag) 347 return []params.StorageAttachmentId{{ 348 StorageTag: storageTag.String(), 349 UnitTag: unitTag.String(), 350 }}, nil 351 }, 352 storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) { 353 c.Assert(u, gc.Equals, unitTag) 354 c.Assert(s, gc.Equals, storageTag) 355 return params.StorageAttachment{}, ¶ms.Error{ 356 Message: "not provisioned", 357 Code: params.CodeNotProvisioned, 358 } 359 }, 360 destroyUnitStorageAttachments: func(u names.UnitTag) error { 361 c.Assert(u, gc.Equals, unitTag) 362 destroyed = true 363 return nil 364 }, 365 remove: func(s names.StorageTag, u names.UnitTag) error { 366 c.Assert(removed, jc.IsFalse) 367 c.Assert(s, gc.Equals, storageTag) 368 c.Assert(u, gc.Equals, unitTag) 369 removed = true 370 return nil 371 }, 372 } 373 374 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 375 c.Assert(err, jc.ErrorIsNil) 376 c.Assert(att.Pending(), gc.Equals, 1) 377 r := storage.NewResolver(att, s.modelType) 378 379 // Inform the resolver that the unit is Dying. The storage is still 380 // Alive, and is now provisioned, but will be destroyed and removed 381 // by the resolver. 382 localState := resolver.LocalState{State: operation.State{ 383 Kind: operation.Continue, 384 }} 385 _, err = r.NextOp(localState, remotestate.Snapshot{ 386 Life: params.Dying, 387 Storage: map[names.StorageTag]remotestate.StorageSnapshot{ 388 storageTag: { 389 Kind: params.StorageKindBlock, 390 Life: params.Alive, 391 Location: "/dev/sdb", 392 Attached: true, 393 }, 394 }, 395 }, &mockOperations{}) 396 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 397 c.Assert(destroyed, jc.IsTrue) 398 c.Assert(att.Pending(), gc.Equals, 0) 399 c.Assert(removed, jc.IsTrue) 400 } 401 402 func (s *attachmentsSuite) TestAttachmentsWaitPending(c *gc.C) { 403 stateDir := c.MkDir() 404 unitTag := names.NewUnitTag("mysql/0") 405 abort := make(chan struct{}) 406 407 storageTag := names.NewStorageTag("data/0") 408 st := &mockStorageAccessor{ 409 unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) { 410 return nil, nil 411 }, 412 } 413 414 att, err := storage.NewAttachments(st, unitTag, stateDir, abort) 415 c.Assert(err, jc.ErrorIsNil) 416 r := storage.NewResolver(att, s.modelType) 417 418 nextOp := func(installed bool) error { 419 localState := resolver.LocalState{State: operation.State{ 420 Installed: installed, 421 Kind: operation.Continue, 422 }} 423 _, err := r.NextOp(localState, remotestate.Snapshot{ 424 Life: params.Alive, 425 Storage: map[names.StorageTag]remotestate.StorageSnapshot{ 426 storageTag: { 427 Life: params.Alive, 428 Attached: false, 429 }, 430 }, 431 }, &mockOperations{}) 432 return err 433 } 434 435 // Inform the resolver of a new, unprovisioned storage attachment. 436 // For IAAS models, before install, we should wait for its completion; 437 // after install, we should not. 438 err = nextOp(false /* workload not installed */) 439 c.Assert(att.Pending(), gc.Equals, 1) 440 441 if s.modelType == model.IAAS { 442 c.Assert(err, gc.Equals, resolver.ErrWaiting) 443 } else { 444 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 445 } 446 447 err = nextOp(true /* workload installed */) 448 c.Assert(err, gc.Equals, resolver.ErrNoOperation) 449 }