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