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