github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/ec2/ebs_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package ec2_test 5 6 import ( 7 stdcontext "context" 8 "fmt" 9 "sort" 10 "strconv" 11 "time" 12 13 "github.com/aws/aws-sdk-go-v2/aws" 14 awsec2 "github.com/aws/aws-sdk-go-v2/service/ec2" 15 "github.com/aws/aws-sdk-go-v2/service/ec2/types" 16 "github.com/aws/smithy-go" 17 "github.com/juju/errors" 18 "github.com/juju/names/v5" 19 jc "github.com/juju/testing/checkers" 20 gc "gopkg.in/check.v1" 21 22 "github.com/juju/juju/cloud" 23 "github.com/juju/juju/core/constraints" 24 "github.com/juju/juju/core/instance" 25 "github.com/juju/juju/environs" 26 environscloudspec "github.com/juju/juju/environs/cloudspec" 27 "github.com/juju/juju/environs/config" 28 "github.com/juju/juju/environs/context" 29 "github.com/juju/juju/environs/tags" 30 "github.com/juju/juju/provider/common" 31 "github.com/juju/juju/provider/ec2" 32 ec2test "github.com/juju/juju/provider/ec2/internal/testing" 33 "github.com/juju/juju/storage" 34 "github.com/juju/juju/testing" 35 ) 36 37 type ebsSuite struct { 38 testing.BaseSuite 39 srv localServer 40 modelConfig *config.Config 41 42 cloudCallCtx context.ProviderCallContext 43 } 44 45 var _ = gc.Suite(&ebsSuite{}) 46 47 func (s *ebsSuite) SetUpTest(c *gc.C) { 48 s.BaseSuite.SetUpTest(c) 49 s.PatchValue(&ec2.DestroyVolumeAttempt.Delay, time.Duration(0)) 50 51 modelConfig, err := config.New(config.NoDefaults, testing.FakeConfig().Merge( 52 testing.Attrs{"type": "ec2"}, 53 )) 54 c.Assert(err, jc.ErrorIsNil) 55 s.modelConfig = modelConfig 56 57 s.srv.startServer(c) 58 s.AddCleanup(func(c *gc.C) { s.srv.stopServer(c) }) 59 60 restoreEC2Patching := patchEC2ForTesting(c, s.srv.region) 61 s.AddCleanup(func(c *gc.C) { restoreEC2Patching() }) 62 63 s.cloudCallCtx = context.NewEmptyCloudCallContext() 64 } 65 66 func (s *ebsSuite) ebsProvider(c *gc.C) storage.Provider { 67 provider, err := environs.Provider("ec2") 68 c.Assert(err, jc.ErrorIsNil) 69 70 credential := cloud.NewCredential( 71 cloud.AccessKeyAuthType, 72 map[string]string{ 73 "access-key": "x", 74 "secret-key": "x", 75 }, 76 ) 77 clientFunc := func(ctx stdcontext.Context, spec environscloudspec.CloudSpec, options ...ec2.ClientOption) (ec2.Client, error) { 78 c.Assert(spec.Region, gc.Equals, "test") 79 return s.srv.ec2srv, nil 80 } 81 82 ctx := stdcontext.WithValue(s.cloudCallCtx, ec2.AWSClientContextKey, clientFunc) 83 env, err := environs.Open(ctx, provider, environs.OpenParams{ 84 Cloud: environscloudspec.CloudSpec{ 85 Type: "ec2", 86 Name: "ec2test", 87 Region: *s.srv.region.RegionName, 88 Endpoint: *s.srv.region.Endpoint, 89 Credential: &credential, 90 }, 91 Config: s.modelConfig, 92 }) 93 c.Assert(err, jc.ErrorIsNil) 94 95 p, err := env.StorageProvider(ec2.EBS_ProviderType) 96 c.Assert(err, jc.ErrorIsNil) 97 return p 98 } 99 100 func (s *ebsSuite) TestValidateConfigUnknownConfig(c *gc.C) { 101 p := s.ebsProvider(c) 102 cfg, err := storage.NewConfig("foo", ec2.EBS_ProviderType, map[string]interface{}{ 103 "unknown": "config", 104 }) 105 c.Assert(err, jc.ErrorIsNil) 106 err = p.ValidateConfig(cfg) 107 c.Assert(err, jc.ErrorIsNil) // unknown attrs ignored 108 } 109 110 func (s *ebsSuite) TestSupports(c *gc.C) { 111 p := s.ebsProvider(c) 112 c.Assert(p.Supports(storage.StorageKindBlock), jc.IsTrue) 113 c.Assert(p.Supports(storage.StorageKindFilesystem), jc.IsFalse) 114 } 115 116 func (s *ebsSuite) volumeSource(c *gc.C, cfg *storage.Config) storage.VolumeSource { 117 p := s.ebsProvider(c) 118 vs, err := p.VolumeSource(cfg) 119 c.Assert(err, jc.ErrorIsNil) 120 return vs 121 } 122 123 func (s *ebsSuite) createVolumesParams(c *gc.C, instanceId string) []storage.VolumeParams { 124 if instanceId == "" { 125 inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil) 126 c.Assert(err, jc.ErrorIsNil) 127 instanceId = inst[0] 128 } 129 volume0 := names.NewVolumeTag("0") 130 volume1 := names.NewVolumeTag("1") 131 volume2 := names.NewVolumeTag("2") 132 volume3 := names.NewVolumeTag("3") 133 volume4 := names.NewVolumeTag("4") 134 volume5 := names.NewVolumeTag("5") 135 params := []storage.VolumeParams{{ 136 Tag: volume0, 137 Size: 10 * 1000, 138 Provider: ec2.EBS_ProviderType, 139 Attributes: map[string]interface{}{ 140 "volume-type": "io1", 141 "iops": 30, 142 }, 143 Attachment: &storage.VolumeAttachmentParams{ 144 AttachmentParams: storage.AttachmentParams{ 145 InstanceId: instance.Id(instanceId), 146 }, 147 }, 148 ResourceTags: map[string]string{ 149 tags.JujuModel: s.modelConfig.UUID(), 150 }, 151 }, { 152 Tag: volume1, 153 Size: 20 * 1000, 154 Provider: ec2.EBS_ProviderType, 155 Attachment: &storage.VolumeAttachmentParams{ 156 AttachmentParams: storage.AttachmentParams{ 157 InstanceId: instance.Id(instanceId), 158 }, 159 }, 160 ResourceTags: map[string]string{ 161 tags.JujuModel: "something-else", 162 }, 163 }, { 164 Tag: volume2, 165 Size: 30 * 1000, 166 Provider: ec2.EBS_ProviderType, 167 ResourceTags: map[string]string{ 168 "abc": "123", 169 }, 170 Attachment: &storage.VolumeAttachmentParams{ 171 AttachmentParams: storage.AttachmentParams{ 172 InstanceId: instance.Id(instanceId), 173 }, 174 }, 175 }, { 176 Tag: volume3, 177 Size: 40 * 1000, 178 Provider: ec2.EBS_ProviderType, 179 ResourceTags: map[string]string{ 180 "volume-type": "st1", 181 }, 182 Attachment: &storage.VolumeAttachmentParams{ 183 AttachmentParams: storage.AttachmentParams{ 184 InstanceId: instance.Id(instanceId), 185 }, 186 }, 187 }, { 188 Tag: volume4, 189 Size: 50 * 1024, 190 Provider: ec2.EBS_ProviderType, 191 ResourceTags: map[string]string{ 192 "volume-type": "sc1", 193 }, 194 Attachment: &storage.VolumeAttachmentParams{ 195 AttachmentParams: storage.AttachmentParams{ 196 InstanceId: instance.Id(instanceId), 197 }, 198 }, 199 }, { 200 Tag: volume5, 201 Size: 60 * 1024, 202 Provider: ec2.EBS_ProviderType, 203 ResourceTags: map[string]string{ 204 "volume-type": "gp3", 205 "encrypted": "true", 206 "kms-key-id": "123456789", 207 "throughput": "500M", 208 }, 209 Attachment: &storage.VolumeAttachmentParams{ 210 AttachmentParams: storage.AttachmentParams{ 211 InstanceId: instance.Id(instanceId), 212 }, 213 }, 214 }} 215 return params 216 } 217 218 func (s *ebsSuite) createVolumes(c *gc.C, vs storage.VolumeSource, instanceId string) ([]storage.CreateVolumesResult, error) { 219 return vs.CreateVolumes(s.cloudCallCtx, s.createVolumesParams(c, instanceId)) 220 } 221 222 func (s *ebsSuite) assertCreateVolumes(c *gc.C, vs storage.VolumeSource, instanceId string) { 223 results, err := s.createVolumes(c, vs, instanceId) 224 c.Assert(err, jc.ErrorIsNil) 225 c.Assert(results, gc.HasLen, 6) 226 c.Assert(results[0].Volume, jc.DeepEquals, &storage.Volume{ 227 names.NewVolumeTag("0"), 228 storage.VolumeInfo{ 229 Size: 10240, 230 VolumeId: "vol-0", 231 Persistent: true, 232 }, 233 }) 234 c.Assert(results[1].Volume, jc.DeepEquals, &storage.Volume{ 235 names.NewVolumeTag("1"), 236 storage.VolumeInfo{ 237 Size: 20480, 238 VolumeId: "vol-1", 239 Persistent: true, 240 }, 241 }) 242 c.Assert(results[2].Volume, jc.DeepEquals, &storage.Volume{ 243 names.NewVolumeTag("2"), 244 storage.VolumeInfo{ 245 Size: 30720, 246 VolumeId: "vol-2", 247 Persistent: true, 248 }, 249 }) 250 c.Assert(results[3].Volume, jc.DeepEquals, &storage.Volume{ 251 names.NewVolumeTag("3"), 252 storage.VolumeInfo{ 253 Size: 40960, 254 VolumeId: "vol-3", 255 Persistent: true, 256 }, 257 }) 258 c.Assert(results[4].Volume, jc.DeepEquals, &storage.Volume{ 259 names.NewVolumeTag("4"), 260 storage.VolumeInfo{ 261 Size: 51200, 262 VolumeId: "vol-4", 263 Persistent: true, 264 }, 265 }) 266 c.Assert(results[5].Volume, jc.DeepEquals, &storage.Volume{ 267 names.NewVolumeTag("5"), 268 storage.VolumeInfo{ 269 Size: 61440, 270 VolumeId: "vol-5", 271 Persistent: true, 272 }, 273 }) 274 ec2Client := ec2.StorageEC2(vs) 275 ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil) 276 c.Assert(err, jc.ErrorIsNil) 277 c.Assert(ec2Vols.Volumes, gc.HasLen, 6) 278 sortBySize(ec2Vols.Volumes) 279 c.Assert(aws.ToInt32(ec2Vols.Volumes[0].Size), gc.Equals, int32(10)) 280 c.Assert(aws.ToInt32(ec2Vols.Volumes[1].Size), gc.Equals, int32(20)) 281 c.Assert(aws.ToInt32(ec2Vols.Volumes[2].Size), gc.Equals, int32(30)) 282 c.Assert(aws.ToInt32(ec2Vols.Volumes[3].Size), gc.Equals, int32(40)) 283 c.Assert(aws.ToInt32(ec2Vols.Volumes[4].Size), gc.Equals, int32(50)) 284 c.Assert(aws.ToInt32(ec2Vols.Volumes[5].Size), gc.Equals, int32(60)) 285 } 286 287 type volumeSorter struct { 288 vols []types.Volume 289 less func(i, j types.Volume) bool 290 } 291 292 func sortBySize(vols []types.Volume) { 293 sort.Sort(volumeSorter{vols, func(i, j types.Volume) bool { 294 return aws.ToInt32(i.Size) < aws.ToInt32(j.Size) 295 }}) 296 } 297 298 func (s volumeSorter) Len() int { 299 return len(s.vols) 300 } 301 302 func (s volumeSorter) Swap(i, j int) { 303 s.vols[i], s.vols[j] = s.vols[j], s.vols[i] 304 } 305 306 func (s volumeSorter) Less(i, j int) bool { 307 return s.less(s.vols[i], s.vols[j]) 308 } 309 310 func (s *ebsSuite) TestCreateVolumes(c *gc.C) { 311 vs := s.volumeSource(c, nil) 312 s.assertCreateVolumes(c, vs, "") 313 } 314 315 func (s *ebsSuite) TestVolumeTags(c *gc.C) { 316 vs := s.volumeSource(c, nil) 317 results, err := s.createVolumes(c, vs, "") 318 c.Assert(err, jc.ErrorIsNil) 319 c.Assert(results, gc.HasLen, 6) 320 c.Assert(results[0].Error, jc.ErrorIsNil) 321 c.Assert(results[0].Volume, jc.DeepEquals, &storage.Volume{ 322 names.NewVolumeTag("0"), 323 storage.VolumeInfo{ 324 Size: 10240, 325 VolumeId: "vol-0", 326 Persistent: true, 327 }, 328 }) 329 c.Assert(results[1].Error, jc.ErrorIsNil) 330 c.Assert(results[1].Volume, jc.DeepEquals, &storage.Volume{ 331 names.NewVolumeTag("1"), 332 storage.VolumeInfo{ 333 Size: 20480, 334 VolumeId: "vol-1", 335 Persistent: true, 336 }, 337 }) 338 c.Assert(results[2].Error, jc.ErrorIsNil) 339 c.Assert(results[2].Volume, jc.DeepEquals, &storage.Volume{ 340 names.NewVolumeTag("2"), 341 storage.VolumeInfo{ 342 Size: 30720, 343 VolumeId: "vol-2", 344 Persistent: true, 345 }, 346 }) 347 c.Assert(results[3].Error, jc.ErrorIsNil) 348 c.Assert(results[3].Volume, jc.DeepEquals, &storage.Volume{ 349 names.NewVolumeTag("3"), 350 storage.VolumeInfo{ 351 Size: 40960, 352 VolumeId: "vol-3", 353 Persistent: true, 354 }, 355 }) 356 c.Assert(results[4].Error, jc.ErrorIsNil) 357 c.Assert(results[4].Volume, jc.DeepEquals, &storage.Volume{ 358 names.NewVolumeTag("4"), 359 storage.VolumeInfo{ 360 Size: 51200, 361 VolumeId: "vol-4", 362 Persistent: true, 363 }, 364 }) 365 c.Assert(results[5].Error, jc.ErrorIsNil) 366 c.Assert(results[5].Volume, jc.DeepEquals, &storage.Volume{ 367 names.NewVolumeTag("5"), 368 storage.VolumeInfo{ 369 Size: 61440, 370 VolumeId: "vol-5", 371 Persistent: true, 372 }, 373 }) 374 ec2Client := ec2.StorageEC2(vs) 375 ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil) 376 c.Assert(err, jc.ErrorIsNil) 377 c.Assert(ec2Vols.Volumes, gc.HasLen, 6) 378 sortBySize(ec2Vols.Volumes) 379 compareTags(c, ec2Vols.Volumes[0].Tags, []tagInfo{ 380 {"juju-model-uuid", "deadbeef-0bad-400d-8000-4b1d0d06f00d"}, 381 {"Name", "juju-testmodel-volume-0"}, 382 }) 383 compareTags(c, ec2Vols.Volumes[1].Tags, []tagInfo{ 384 {"juju-model-uuid", "something-else"}, 385 {"Name", "juju-testmodel-volume-1"}, 386 }) 387 compareTags(c, ec2Vols.Volumes[2].Tags, []tagInfo{ 388 {"Name", "juju-testmodel-volume-2"}, 389 {"abc", "123"}, 390 }) 391 compareTags(c, ec2Vols.Volumes[3].Tags, []tagInfo{ 392 {"Name", "juju-testmodel-volume-3"}, 393 {"volume-type", "st1"}, 394 }) 395 compareTags(c, ec2Vols.Volumes[4].Tags, []tagInfo{ 396 {"Name", "juju-testmodel-volume-4"}, 397 {"volume-type", "sc1"}, 398 }) 399 compareTags(c, ec2Vols.Volumes[5].Tags, []tagInfo{ 400 {"Name", "juju-testmodel-volume-5"}, 401 {"volume-type", "gp3"}, 402 {"encrypted", "true"}, 403 {"kms-key-id", "123456789"}, 404 {"throughput", "500M"}, 405 }) 406 } 407 408 func (s *ebsSuite) TestVolumeTypeAliases(c *gc.C) { 409 inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil) 410 c.Assert(err, jc.ErrorIsNil) 411 instanceIdRunning := inst[0] 412 vs := s.volumeSource(c, nil) 413 ec2Client := ec2.StorageEC2(vs) 414 aliases := [][2]string{ 415 {"magnetic", "standard"}, 416 {"cold-storage", "sc1"}, 417 {"optimized-hdd", "st1"}, 418 {"ssd", "gp2"}, 419 {"provisioned-iops", "io1"}, 420 } 421 for i, alias := range aliases { 422 params := []storage.VolumeParams{{ 423 Tag: names.NewVolumeTag("0"), 424 Size: 500 * 1024, 425 Provider: ec2.EBS_ProviderType, 426 Attributes: map[string]interface{}{ 427 "volume-type": alias[0], 428 }, 429 Attachment: &storage.VolumeAttachmentParams{ 430 AttachmentParams: storage.AttachmentParams{ 431 InstanceId: instance.Id(instanceIdRunning), 432 }, 433 }, 434 }} 435 if alias[1] == "io1" { 436 params[0].Attributes["iops"] = 30 437 } 438 results, err := vs.CreateVolumes(s.cloudCallCtx, params) 439 c.Assert(err, jc.ErrorIsNil) 440 c.Assert(results, gc.HasLen, 1) 441 c.Assert(results[0].Error, jc.ErrorIsNil) 442 c.Assert(results[0].Volume.VolumeId, gc.Equals, fmt.Sprintf("vol-%d", i)) 443 } 444 ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil) 445 c.Assert(err, jc.ErrorIsNil) 446 c.Assert(ec2Vols.Volumes, gc.HasLen, len(aliases)) 447 sort.Sort(volumeSorter{ec2Vols.Volumes, func(i, j types.Volume) bool { 448 return aws.ToString(i.VolumeId) < aws.ToString(j.VolumeId) 449 }}) 450 for i, alias := range aliases { 451 c.Assert(string(ec2Vols.Volumes[i].VolumeType), gc.Equals, alias[1]) 452 } 453 } 454 455 func (s *ebsSuite) TestDestroyVolumesNotFoundReturnsNil(c *gc.C) { 456 vs := s.volumeSource(c, nil) 457 results, err := vs.DestroyVolumes(s.cloudCallCtx, []string{"vol-42"}) 458 c.Assert(err, jc.ErrorIsNil) 459 c.Assert(results, gc.HasLen, 1) 460 c.Assert(results[0], jc.ErrorIsNil) 461 } 462 463 func (s *ebsSuite) TestDestroyVolumesCredentialError(c *gc.C) { 464 vs := s.volumeSource(c, nil) 465 s.setupAttachVolumesTest(c, vs, ec2test.Running) 466 467 s.srv.ec2srv.SetAPIError("DeleteVolume", &smithy.GenericAPIError{Code: "Blocked"}) 468 469 in := []string{"vol-0"} 470 results, err := vs.DestroyVolumes(s.cloudCallCtx, in) 471 c.Assert(err, jc.ErrorIsNil) 472 c.Assert(results, gc.HasLen, len(in)) 473 for i, result := range results { 474 c.Logf("checking volume deletion %d", i) 475 c.Assert(errors.Is(result, common.ErrorCredentialNotValid), jc.IsTrue) 476 } 477 } 478 479 func (s *ebsSuite) TestDestroyVolumes(c *gc.C) { 480 vs := s.volumeSource(c, nil) 481 s.setupAttachVolumesTest(c, vs, ec2test.Running) 482 errs, err := vs.DestroyVolumes(s.cloudCallCtx, []string{"vol-0"}) 483 c.Assert(err, jc.ErrorIsNil) 484 c.Assert(errs, jc.DeepEquals, []error{nil}) 485 486 ec2Client := ec2.StorageEC2(vs) 487 ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil) 488 c.Assert(err, jc.ErrorIsNil) 489 c.Assert(ec2Vols.Volumes, gc.HasLen, 5) 490 sortBySize(ec2Vols.Volumes) 491 c.Assert(aws.ToInt32(ec2Vols.Volumes[0].Size), gc.Equals, int32(20)) 492 c.Assert(aws.ToInt32(ec2Vols.Volumes[1].Size), gc.Equals, int32(30)) 493 c.Assert(aws.ToInt32(ec2Vols.Volumes[2].Size), gc.Equals, int32(40)) 494 c.Assert(aws.ToInt32(ec2Vols.Volumes[3].Size), gc.Equals, int32(50)) 495 } 496 497 func (s *ebsSuite) TestDestroyVolumesStillAttached(c *gc.C) { 498 vs := s.volumeSource(c, nil) 499 params := s.setupAttachVolumesTest(c, vs, ec2test.Running) 500 _, err := vs.AttachVolumes(s.cloudCallCtx, params) 501 c.Assert(err, jc.ErrorIsNil) 502 errs, err := vs.DestroyVolumes(s.cloudCallCtx, []string{"vol-0"}) 503 c.Assert(err, jc.ErrorIsNil) 504 c.Assert(errs, jc.DeepEquals, []error{nil}) 505 506 ec2Client := ec2.StorageEC2(vs) 507 ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil) 508 c.Assert(err, jc.ErrorIsNil) 509 c.Assert(ec2Vols.Volumes, gc.HasLen, 5) 510 sortBySize(ec2Vols.Volumes) 511 c.Assert(aws.ToInt32(ec2Vols.Volumes[0].Size), gc.Equals, int32(20)) 512 c.Assert(aws.ToInt32(ec2Vols.Volumes[2].Size), gc.Equals, int32(40)) 513 c.Assert(aws.ToInt32(ec2Vols.Volumes[3].Size), gc.Equals, int32(50)) 514 } 515 516 func (s *ebsSuite) TestReleaseVolumes(c *gc.C) { 517 vs := s.volumeSource(c, nil) 518 s.setupAttachVolumesTest(c, vs, ec2test.Running) 519 errs, err := vs.ReleaseVolumes(s.cloudCallCtx, []string{"vol-0"}) 520 c.Assert(err, jc.ErrorIsNil) 521 c.Assert(errs, jc.DeepEquals, []error{nil}) 522 523 ec2Client := ec2.StorageEC2(vs) 524 ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, &awsec2.DescribeVolumesInput{ 525 VolumeIds: []string{"vol-0"}, 526 }) 527 c.Assert(err, jc.ErrorIsNil) 528 c.Assert(ec2Vols.Volumes, gc.HasLen, 1) 529 compareTags(c, ec2Vols.Volumes[0].Tags, []tagInfo{ 530 {"juju-controller-uuid", ""}, 531 {"juju-model-uuid", ""}, 532 {"Name", "juju-testmodel-volume-0"}, 533 }) 534 } 535 536 func (s *ebsSuite) TestReleaseVolumesCredentialError(c *gc.C) { 537 vs := s.volumeSource(c, nil) 538 s.setupAttachVolumesTest(c, vs, ec2test.Running) 539 540 s.srv.ec2srv.SetAPIError("DescribeVolumes", &smithy.GenericAPIError{Code: "Blocked"}) 541 in := []string{"vol-0"} 542 results, err := vs.ReleaseVolumes(s.cloudCallCtx, in) 543 c.Assert(err, jc.ErrorIsNil) 544 c.Assert(results, gc.HasLen, len(in)) 545 for i, result := range results { 546 c.Logf("checking volume release %d", i) 547 c.Assert(errors.Is(result, common.ErrorCredentialNotValid), jc.IsTrue) 548 } 549 } 550 551 func (s *ebsSuite) TestReleaseVolumesStillAttached(c *gc.C) { 552 vs := s.volumeSource(c, nil) 553 params := s.setupAttachVolumesTest(c, vs, ec2test.Running) 554 _, err := vs.AttachVolumes(s.cloudCallCtx, params) 555 c.Assert(err, jc.ErrorIsNil) 556 errs, err := vs.ReleaseVolumes(s.cloudCallCtx, []string{"vol-0"}) 557 c.Assert(err, jc.ErrorIsNil) 558 c.Assert(errs, gc.HasLen, 1) 559 c.Assert(errs[0], gc.ErrorMatches, `cannot release volume "vol-0": attachments still active`) 560 561 ec2Client := ec2.StorageEC2(vs) 562 ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, &awsec2.DescribeVolumesInput{ 563 VolumeIds: []string{"vol-0"}, 564 }) 565 c.Assert(err, jc.ErrorIsNil) 566 c.Assert(ec2Vols.Volumes, gc.HasLen, 1) 567 compareTags(c, ec2Vols.Volumes[0].Tags, []tagInfo{ 568 {"juju-model-uuid", "deadbeef-0bad-400d-8000-4b1d0d06f00d"}, 569 {"Name", "juju-testmodel-volume-0"}, 570 }) 571 } 572 573 func (s *ebsSuite) TestAttachVolumesCredentialError(c *gc.C) { 574 vs := s.volumeSource(c, nil) 575 params := s.setupAttachVolumesTest(c, vs, ec2test.Running) 576 577 s.srv.ec2srv.SetAPIError("AttachVolume", &smithy.GenericAPIError{Code: "Blocked"}) 578 579 results, err := vs.AttachVolumes(s.cloudCallCtx, params) 580 c.Assert(err, jc.ErrorIsNil) 581 c.Assert(results, gc.HasLen, 1) 582 c.Assert(errors.Is(results[0].Error, common.ErrorCredentialNotValid), jc.IsTrue) 583 } 584 585 func (s *ebsSuite) TestReleaseVolumesNotFound(c *gc.C) { 586 vs := s.volumeSource(c, nil) 587 errs, err := vs.ReleaseVolumes(s.cloudCallCtx, []string{"vol-42"}) 588 c.Assert(err, jc.ErrorIsNil) 589 c.Assert(errs, gc.HasLen, 1) 590 c.Assert(errs[0], gc.ErrorMatches, `cannot release volume "vol-42": vol-42 not found`) 591 } 592 593 func (s *ebsSuite) TestDescribeVolumes(c *gc.C) { 594 vs := s.volumeSource(c, nil) 595 s.assertCreateVolumes(c, vs, "") 596 597 vols, err := vs.DescribeVolumes(s.cloudCallCtx, []string{"vol-0", "vol-1"}) 598 c.Assert(err, jc.ErrorIsNil) 599 c.Assert(vols, jc.DeepEquals, []storage.DescribeVolumesResult{{ 600 VolumeInfo: &storage.VolumeInfo{ 601 Size: 10240, 602 VolumeId: "vol-0", 603 Persistent: true, 604 }, 605 }, { 606 VolumeInfo: &storage.VolumeInfo{ 607 Size: 20480, 608 VolumeId: "vol-1", 609 Persistent: true, 610 }, 611 }}) 612 } 613 614 func (s *ebsSuite) TestDescribeVolumesNotFound(c *gc.C) { 615 vs := s.volumeSource(c, nil) 616 vols, err := vs.DescribeVolumes(s.cloudCallCtx, []string{"vol-42"}) 617 c.Assert(err, jc.ErrorIsNil) 618 c.Assert(vols, gc.HasLen, 1) 619 c.Assert(vols[0].Error, gc.ErrorMatches, "vol-42 not found") 620 } 621 622 func (s *ebsSuite) TestDescribeVolumesCredentialError(c *gc.C) { 623 vs := s.volumeSource(c, nil) 624 625 s.srv.ec2srv.SetAPIError("DescribeVolumes", &smithy.GenericAPIError{Code: "Blocked"}) 626 627 results, err := vs.DescribeVolumes(s.cloudCallCtx, []string{"vol-42"}) 628 c.Assert(errors.Is(err, common.ErrorCredentialNotValid), jc.IsTrue) 629 c.Assert(results, gc.IsNil) 630 } 631 632 func (s *ebsSuite) TestListVolumes(c *gc.C) { 633 vs := s.volumeSource(c, nil) 634 s.assertCreateVolumes(c, vs, "") 635 636 // Only one volume created by assertCreateVolumes has 637 // the model-uuid tag with the expected value. 638 volIds, err := vs.ListVolumes(s.cloudCallCtx) 639 c.Assert(err, jc.ErrorIsNil) 640 c.Assert(volIds, jc.SameContents, []string{"vol-0"}) 641 } 642 643 func (s *ebsSuite) TestListVolumesCredentialError(c *gc.C) { 644 vs := s.volumeSource(c, nil) 645 646 s.srv.ec2srv.SetAPIError("DescribeVolumes", &smithy.GenericAPIError{Code: "Blocked"}) 647 648 results, err := vs.ListVolumes(s.cloudCallCtx) 649 c.Assert(errors.Is(err, common.ErrorCredentialNotValid), jc.IsTrue) 650 c.Assert(results, gc.IsNil) 651 } 652 653 func (s *ebsSuite) TestListVolumesIgnoresRootDisks(c *gc.C) { 654 s.srv.ec2srv.SetCreateRootDisks(true) 655 s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil) 656 657 // Tag the root disk with the model UUID. 658 _, err := s.srv.ec2srv.CreateTags(s.cloudCallCtx, &awsec2.CreateTagsInput{ 659 Resources: []string{"vol-0"}, 660 Tags: []types.Tag{ 661 {Key: aws.String(tags.JujuModel), Value: aws.String(s.modelConfig.UUID())}, 662 }, 663 }) 664 c.Assert(err, jc.ErrorIsNil) 665 666 vs := s.volumeSource(c, nil) 667 volIds, err := vs.ListVolumes(s.cloudCallCtx) 668 c.Assert(err, jc.ErrorIsNil) 669 c.Assert(volIds, gc.HasLen, 0) 670 } 671 672 func (s *ebsSuite) TestCreateVolumesErrors(c *gc.C) { 673 vs := s.volumeSource(c, nil) 674 volume0 := names.NewVolumeTag("0") 675 676 inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil) 677 c.Assert(err, jc.ErrorIsNil) 678 instanceIdPending := inst[0] 679 inst, err = s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil) 680 instanceIdRunning := inst[0] 681 attachmentParams := storage.VolumeAttachmentParams{ 682 AttachmentParams: storage.AttachmentParams{ 683 InstanceId: instance.Id(instanceIdRunning), 684 }, 685 } 686 687 for _, test := range []struct { 688 params storage.VolumeParams 689 err string 690 }{{ 691 params: storage.VolumeParams{ 692 Size: 1024, 693 Provider: ec2.EBS_ProviderType, 694 Attachment: &storage.VolumeAttachmentParams{ 695 AttachmentParams: storage.AttachmentParams{ 696 InstanceId: "woat", 697 }, 698 }, 699 }, 700 err: `querying instance details: api error InvalidInstanceID.NotFound: instance "woat" not found`, 701 }, { 702 params: storage.VolumeParams{ 703 Size: 1024, 704 Provider: ec2.EBS_ProviderType, 705 Attachment: &storage.VolumeAttachmentParams{ 706 AttachmentParams: storage.AttachmentParams{ 707 InstanceId: instance.Id(instanceIdPending), 708 }, 709 }, 710 }, 711 err: "cannot attach to non-running instance i-3", 712 }, { 713 params: storage.VolumeParams{ 714 Size: 100000000, 715 Provider: ec2.EBS_ProviderType, 716 Attributes: map[string]interface{}{}, 717 Attachment: &attachmentParams, 718 }, 719 err: "volume size 97657 GiB exceeds the maximum of 16384 GiB", 720 }, { 721 params: storage.VolumeParams{ 722 Size: 100000000, 723 Provider: ec2.EBS_ProviderType, 724 Attributes: map[string]interface{}{ 725 "volume-type": "gp2", 726 }, 727 Attachment: &attachmentParams, 728 }, 729 err: "volume size 97657 GiB exceeds the maximum of 16384 GiB", 730 }, { 731 params: storage.VolumeParams{ 732 Size: 100000000, 733 Provider: ec2.EBS_ProviderType, 734 Attributes: map[string]interface{}{ 735 "volume-type": "io1", 736 "iops": "30", 737 }, 738 Attachment: &attachmentParams, 739 }, 740 err: "volume size 97657 GiB exceeds the maximum of 16384 GiB", 741 }, { 742 params: storage.VolumeParams{ 743 Tag: volume0, 744 Size: 1000, 745 Provider: ec2.EBS_ProviderType, 746 Attributes: map[string]interface{}{ 747 "volume-type": "io1", 748 "iops": "30", 749 }, 750 Attachment: &attachmentParams, 751 }, 752 err: "volume size is 1 GiB, must be at least 4 GiB", 753 }, { 754 params: storage.VolumeParams{ 755 Tag: volume0, 756 Size: 10000, 757 Provider: ec2.EBS_ProviderType, 758 Attributes: map[string]interface{}{ 759 "volume-type": "io1", 760 "iops": "1234", 761 }, 762 Attachment: &attachmentParams, 763 }, 764 err: "specified IOPS ratio is 1234/GiB, maximum is 30/GiB", 765 }, { 766 params: storage.VolumeParams{ 767 Tag: volume0, 768 Size: 10000, 769 Provider: ec2.EBS_ProviderType, 770 Attributes: map[string]interface{}{ 771 "volume-type": "standard", 772 "iops": "30", 773 }, 774 Attachment: &attachmentParams, 775 }, 776 err: `IOPS specified, but volume type is "standard"`, 777 }, { 778 params: storage.VolumeParams{ 779 Tag: volume0, 780 Size: 10000, 781 Provider: ec2.EBS_ProviderType, 782 Attributes: map[string]interface{}{ 783 "volume-type": "what", 784 }, 785 Attachment: &attachmentParams, 786 }, 787 err: "validating EBS storage config: volume-type: unexpected value \"what\"", 788 }, { 789 params: storage.VolumeParams{ 790 Tag: volume0, 791 Size: 400 * 1024, 792 Provider: ec2.EBS_ProviderType, 793 Attributes: map[string]interface{}{ 794 "volume-type": "st1", 795 }, 796 Attachment: &attachmentParams, 797 }, 798 err: "volume size is 400 GiB, must be at least 500 GiB", 799 }, { 800 params: storage.VolumeParams{ 801 Tag: volume0, 802 Size: 17 * 1024 * 1024, 803 Provider: ec2.EBS_ProviderType, 804 Attributes: map[string]interface{}{ 805 "volume-type": "st1", 806 }, 807 Attachment: &attachmentParams, 808 }, 809 err: "volume size 17408 GiB exceeds the maximum of 16384 GiB", 810 }, { 811 params: storage.VolumeParams{ 812 Tag: volume0, 813 Size: 10000, 814 Provider: ec2.EBS_ProviderType, 815 Attributes: map[string]interface{}{ 816 "volume-type": "st1", 817 "iops": "30", 818 }, 819 Attachment: &attachmentParams, 820 }, 821 err: `IOPS specified, but volume type is "st1"`, 822 }, { 823 params: storage.VolumeParams{ 824 Tag: volume0, 825 Size: 300 * 1024, 826 Provider: ec2.EBS_ProviderType, 827 Attributes: map[string]interface{}{ 828 "volume-type": "sc1", 829 }, 830 Attachment: &attachmentParams, 831 }, 832 err: "volume size is 300 GiB, must be at least 500 GiB", 833 }, { 834 params: storage.VolumeParams{ 835 Tag: volume0, 836 Size: 18 * 1024 * 1024, 837 Provider: ec2.EBS_ProviderType, 838 Attributes: map[string]interface{}{ 839 "volume-type": "sc1", 840 }, 841 Attachment: &attachmentParams, 842 }, 843 err: "volume size 18432 GiB exceeds the maximum of 16384 GiB", 844 }, { 845 params: storage.VolumeParams{ 846 Tag: volume0, 847 Size: 10000, 848 Provider: ec2.EBS_ProviderType, 849 Attributes: map[string]interface{}{ 850 "volume-type": "sc1", 851 "iops": "30", 852 }, 853 Attachment: &attachmentParams, 854 }, 855 err: `IOPS specified, but volume type is "sc1"`, 856 }, { 857 params: storage.VolumeParams{ 858 Tag: volume0, 859 Size: 10000, 860 Provider: ec2.EBS_ProviderType, 861 Attributes: map[string]interface{}{ 862 "volume-type": "gp2", 863 "throughput": "30", 864 }, 865 Attachment: &attachmentParams, 866 }, 867 err: `"throughput" cannot be specified when volume type is "gp2"`, 868 }} { 869 results, err := vs.CreateVolumes(s.cloudCallCtx, []storage.VolumeParams{test.params}) 870 c.Assert(err, jc.ErrorIsNil) 871 c.Assert(results, gc.HasLen, 1) 872 c.Check(results[0].Error, gc.ErrorMatches, test.err) 873 } 874 } 875 876 func (s *ebsSuite) TestCreateVolumesCredentialError(c *gc.C) { 877 vs := s.volumeSource(c, nil) 878 params := s.createVolumesParams(c, "") 879 880 s.srv.ec2srv.SetAPIError("CreateVolume", &smithy.GenericAPIError{Code: "Blocked"}) 881 882 results, err := vs.CreateVolumes(s.cloudCallCtx, params) 883 c.Assert(err, jc.ErrorIsNil) 884 for i, result := range results { 885 c.Logf("checking volume creation %d", i) 886 c.Assert(result.Volume, gc.IsNil) 887 c.Assert(result.VolumeAttachment, gc.IsNil) 888 c.Assert(errors.Is(result.Error, common.ErrorCredentialNotValid), jc.IsTrue) 889 } 890 } 891 892 var imageId = "ami-ccf405a5" // Ubuntu Maverick, i386, EBS store 893 894 func (s *ebsSuite) setupAttachVolumesTest( 895 c *gc.C, vs storage.VolumeSource, state types.InstanceState, 896 ) []storage.VolumeAttachmentParams { 897 898 inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, state, nil) 899 c.Assert(err, jc.ErrorIsNil) 900 instanceId := inst[0] 901 s.assertCreateVolumes(c, vs, instanceId) 902 903 return []storage.VolumeAttachmentParams{{ 904 Volume: names.NewVolumeTag("0"), 905 VolumeId: "vol-0", 906 AttachmentParams: storage.AttachmentParams{ 907 Machine: names.NewMachineTag("1"), 908 InstanceId: instance.Id(instanceId), 909 }, 910 }} 911 } 912 913 func (s *ebsSuite) TestAttachVolumesNotRunning(c *gc.C) { 914 vs := s.volumeSource(c, nil) 915 inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil) 916 c.Assert(err, jc.ErrorIsNil) 917 results, err := s.createVolumes(c, vs, inst[0]) 918 c.Assert(err, jc.ErrorIsNil) 919 c.Assert(results, gc.Not(gc.HasLen), 0) 920 for _, result := range results { 921 c.Check(errors.Cause(result.Error), gc.ErrorMatches, "cannot attach to non-running instance i-3") 922 } 923 } 924 925 func (s *ebsSuite) TestAttachVolumes(c *gc.C) { 926 vs := s.volumeSource(c, nil) 927 params := s.setupAttachVolumesTest(c, vs, ec2test.Running) 928 result, err := vs.AttachVolumes(s.cloudCallCtx, params) 929 c.Assert(err, jc.ErrorIsNil) 930 c.Assert(result, gc.HasLen, 1) 931 c.Assert(result[0].Error, jc.ErrorIsNil) 932 c.Assert(result[0].VolumeAttachment, jc.DeepEquals, &storage.VolumeAttachment{ 933 names.NewVolumeTag("0"), 934 names.NewMachineTag("1"), 935 storage.VolumeAttachmentInfo{ 936 DeviceName: "xvdf", 937 DeviceLink: "/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0", 938 ReadOnly: false, 939 }, 940 }) 941 942 ec2Client := ec2.StorageEC2(vs) 943 ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil) 944 c.Assert(err, jc.ErrorIsNil) 945 c.Assert(ec2Vols.Volumes, gc.HasLen, 6) 946 sortBySize(ec2Vols.Volumes) 947 c.Assert(ec2Vols.Volumes[0].Attachments, jc.DeepEquals, []types.VolumeAttachment{{ 948 VolumeId: aws.String("vol-0"), 949 InstanceId: aws.String("i-3"), 950 Device: aws.String("/dev/sdf"), 951 State: "attached", 952 }}) 953 954 // Test idempotency. 955 result, err = vs.AttachVolumes(s.cloudCallCtx, params) 956 c.Assert(err, jc.ErrorIsNil) 957 c.Assert(result, gc.HasLen, 1) 958 c.Assert(result[0].Error, jc.ErrorIsNil) 959 c.Assert(result[0].VolumeAttachment, jc.DeepEquals, &storage.VolumeAttachment{ 960 names.NewVolumeTag("0"), 961 names.NewMachineTag("1"), 962 storage.VolumeAttachmentInfo{ 963 DeviceName: "xvdf", 964 DeviceLink: "/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0", 965 ReadOnly: false, 966 }, 967 }) 968 } 969 970 func (s *ebsSuite) TestAttachVolumesCreating(c *gc.C) { 971 vs := s.volumeSource(c, nil) 972 params := s.setupAttachVolumesTest(c, vs, ec2test.Running) 973 var calls int 974 s.srv.ec2srv.SetAPIModifiers("DescribeVolumes", func(out interface{}) { 975 out.(*awsec2.DescribeVolumesOutput).Volumes[0].State = "creating" 976 calls++ 977 }, func(out interface{}) { 978 out.(*awsec2.DescribeVolumesOutput).Volumes[0].State = "available" 979 calls++ 980 }) 981 result, err := vs.AttachVolumes(s.cloudCallCtx, params) 982 c.Assert(err, jc.ErrorIsNil) 983 c.Assert(result, gc.HasLen, 1) 984 c.Assert(result[0].Error, jc.ErrorIsNil) 985 c.Assert(calls, gc.Equals, 2) 986 } 987 988 func (s *ebsSuite) TestAttachVolumesDetaching(c *gc.C) { 989 vs := s.volumeSource(c, nil) 990 params := s.setupAttachVolumesTest(c, vs, ec2test.Running) 991 s.srv.ec2srv.SetAPIModifiers("DescribeVolumes", func(out interface{}) { 992 vols := out.(*awsec2.DescribeVolumesOutput).Volumes 993 vols[0].State = "in-use" 994 vols[0].Attachments = append(vols[0].Attachments, types.VolumeAttachment{ 995 InstanceId: aws.String("something else"), 996 }) 997 }) 998 result, err := vs.AttachVolumes(s.cloudCallCtx, params) 999 c.Assert(err, jc.ErrorIsNil) 1000 c.Assert(result, gc.HasLen, 1) 1001 c.Assert(result[0].Error, gc.ErrorMatches, "volume vol-0 is attached to something else") 1002 } 1003 1004 func (s *ebsSuite) TestDetachVolumes(c *gc.C) { 1005 vs := s.volumeSource(c, nil) 1006 params := s.setupAttachVolumesTest(c, vs, ec2test.Running) 1007 _, err := vs.AttachVolumes(s.cloudCallCtx, params) 1008 c.Assert(err, jc.ErrorIsNil) 1009 errs, err := vs.DetachVolumes(s.cloudCallCtx, params) 1010 c.Assert(err, jc.ErrorIsNil) 1011 c.Assert(errs, jc.DeepEquals, []error{nil}) 1012 1013 ec2Client := ec2.StorageEC2(vs) 1014 ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil) 1015 c.Assert(err, jc.ErrorIsNil) 1016 c.Assert(ec2Vols.Volumes, gc.HasLen, 6) 1017 sortBySize(ec2Vols.Volumes) 1018 c.Assert(ec2Vols.Volumes[0].Attachments, gc.HasLen, 0) 1019 1020 // Test idempotent 1021 errs, err = vs.DetachVolumes(s.cloudCallCtx, params) 1022 c.Assert(err, jc.ErrorIsNil) 1023 c.Assert(errs, jc.DeepEquals, []error{nil}) 1024 } 1025 1026 func (s *ebsSuite) TestDetachVolumesIncorrectState(c *gc.C) { 1027 s.testDetachVolumesDetachedState(c, "IncorrectState") 1028 } 1029 1030 func (s *ebsSuite) TestDetachVolumesAttachmentNotFound(c *gc.C) { 1031 s.testDetachVolumesDetachedState(c, "InvalidAttachment.NotFound") 1032 } 1033 1034 func (s *ebsSuite) testDetachVolumesDetachedState(c *gc.C, errorCode string) { 1035 vs := s.volumeSource(c, nil) 1036 params := s.setupAttachVolumesTest(c, vs, ec2test.Running) 1037 _, err := vs.AttachVolumes(s.cloudCallCtx, params) 1038 c.Assert(err, jc.ErrorIsNil) 1039 1040 s.srv.ec2srv.SetAPIError("DetachVolume", &smithy.GenericAPIError{Code: errorCode}) 1041 1042 errs, err := vs.DetachVolumes(s.cloudCallCtx, params) 1043 c.Assert(err, jc.ErrorIsNil) 1044 c.Assert(errs, jc.DeepEquals, []error{nil}) 1045 } 1046 1047 func (s *ebsSuite) TestImportVolume(c *gc.C) { 1048 vs := s.volumeSource(c, nil) 1049 c.Assert(vs, gc.Implements, new(storage.VolumeImporter)) 1050 1051 resp, err := s.srv.ec2srv.CreateVolume(s.cloudCallCtx, &awsec2.CreateVolumeInput{ 1052 Size: aws.Int32(1), 1053 VolumeType: "gp2", 1054 AvailabilityZone: aws.String("us-east-1a"), 1055 }) 1056 c.Assert(err, jc.ErrorIsNil) 1057 1058 volID := aws.ToString(resp.VolumeId) 1059 volInfo, err := vs.(storage.VolumeImporter).ImportVolume(s.cloudCallCtx, volID, map[string]string{ 1060 "foo": "bar", 1061 }) 1062 c.Assert(err, jc.ErrorIsNil) 1063 c.Assert(volInfo, jc.DeepEquals, storage.VolumeInfo{ 1064 VolumeId: volID, 1065 Size: 1024, 1066 Persistent: true, 1067 }) 1068 1069 volumes, err := s.srv.ec2srv.DescribeVolumes(s.cloudCallCtx, &awsec2.DescribeVolumesInput{ 1070 VolumeIds: []string{volID}, 1071 }) 1072 c.Assert(err, jc.ErrorIsNil) 1073 c.Assert(volumes.Volumes, gc.HasLen, 1) 1074 compareTags(c, volumes.Volumes[0].Tags, []tagInfo{ 1075 {"foo", "bar"}, 1076 }) 1077 } 1078 1079 func (s *ebsSuite) TestImportVolumeCredentialError(c *gc.C) { 1080 vs := s.volumeSource(c, nil) 1081 c.Assert(vs, gc.Implements, new(storage.VolumeImporter)) 1082 resp, err := s.srv.ec2srv.CreateVolume(s.cloudCallCtx, &awsec2.CreateVolumeInput{ 1083 Size: aws.Int32(1), 1084 VolumeType: "gp2", 1085 AvailabilityZone: aws.String("us-east-1a"), 1086 }) 1087 c.Assert(err, jc.ErrorIsNil) 1088 1089 s.srv.ec2srv.SetAPIError("CreateTags", &smithy.GenericAPIError{Code: "Blocked"}) 1090 1091 _, err = vs.(storage.VolumeImporter).ImportVolume(s.cloudCallCtx, aws.ToString(resp.VolumeId), map[string]string{ 1092 "foo": "bar", 1093 }) 1094 c.Assert(errors.Is(err, common.ErrorCredentialNotValid), jc.IsTrue) 1095 } 1096 1097 func (s *ebsSuite) TestImportVolumeInUse(c *gc.C) { 1098 vs := s.volumeSource(c, nil) 1099 c.Assert(vs, gc.Implements, new(storage.VolumeImporter)) 1100 1101 params := s.setupAttachVolumesTest(c, vs, ec2test.Running) 1102 _, err := vs.AttachVolumes(s.cloudCallCtx, params) 1103 c.Assert(err, jc.ErrorIsNil) 1104 1105 volId := params[0].VolumeId 1106 _, err = vs.(storage.VolumeImporter).ImportVolume(s.cloudCallCtx, volId, map[string]string{}) 1107 c.Assert(err, gc.ErrorMatches, `cannot import volume with status "in-use"`) 1108 } 1109 1110 type blockDeviceMappingSuite struct { 1111 testing.BaseSuite 1112 } 1113 1114 var _ = gc.Suite(&blockDeviceMappingSuite{}) 1115 1116 func (*blockDeviceMappingSuite) TestBlockDeviceNamer(c *gc.C) { 1117 var nextName func() (string, string, error) 1118 expect := func(expectRequest, expectActual string) { 1119 request, actual, err := nextName() 1120 c.Assert(err, jc.ErrorIsNil) 1121 c.Assert(request, gc.Equals, expectRequest) 1122 c.Assert(actual, gc.Equals, expectActual) 1123 } 1124 expectN := func(expectRequest, expectActual string) { 1125 for i := 1; i <= 6; i++ { 1126 request, actual, err := nextName() 1127 c.Assert(err, jc.ErrorIsNil) 1128 c.Assert(request, gc.Equals, expectRequest+strconv.Itoa(i)) 1129 c.Assert(actual, gc.Equals, expectActual+strconv.Itoa(i)) 1130 } 1131 } 1132 expectErr := func(expectErr string) { 1133 _, _, err := nextName() 1134 c.Assert(err, gc.ErrorMatches, expectErr) 1135 } 1136 1137 // First without numbers. 1138 nextName = ec2.BlockDeviceNamer(false) 1139 expect("/dev/sdf", "xvdf") 1140 expect("/dev/sdg", "xvdg") 1141 expect("/dev/sdh", "xvdh") 1142 expect("/dev/sdi", "xvdi") 1143 expect("/dev/sdj", "xvdj") 1144 expect("/dev/sdk", "xvdk") 1145 expect("/dev/sdl", "xvdl") 1146 expect("/dev/sdm", "xvdm") 1147 expect("/dev/sdn", "xvdn") 1148 expect("/dev/sdo", "xvdo") 1149 expect("/dev/sdp", "xvdp") 1150 expect("/dev/sdq", "xvdq") 1151 expect("/dev/sdr", "xvdr") 1152 expect("/dev/sds", "xvds") 1153 expect("/dev/sdt", "xvdt") 1154 expect("/dev/sdu", "xvdu") 1155 expect("/dev/sdv", "xvdv") 1156 expect("/dev/sdw", "xvdw") 1157 expect("/dev/sdx", "xvdx") 1158 expect("/dev/sdy", "xvdy") 1159 expect("/dev/sdz", "xvdz") 1160 expectErr("too many EBS volumes to attach") 1161 1162 // Now with numbers. 1163 nextName = ec2.BlockDeviceNamer(true) 1164 expect("/dev/sdf1", "xvdf1") 1165 expect("/dev/sdf2", "xvdf2") 1166 expect("/dev/sdf3", "xvdf3") 1167 expect("/dev/sdf4", "xvdf4") 1168 expect("/dev/sdf5", "xvdf5") 1169 expect("/dev/sdf6", "xvdf6") 1170 expectN("/dev/sdg", "xvdg") 1171 expectN("/dev/sdh", "xvdh") 1172 expectN("/dev/sdi", "xvdi") 1173 expectN("/dev/sdj", "xvdj") 1174 expectN("/dev/sdk", "xvdk") 1175 expectN("/dev/sdl", "xvdl") 1176 expectN("/dev/sdm", "xvdm") 1177 expectN("/dev/sdn", "xvdn") 1178 expectN("/dev/sdo", "xvdo") 1179 expectN("/dev/sdp", "xvdp") 1180 expectN("/dev/sdq", "xvdq") 1181 expectN("/dev/sdr", "xvdr") 1182 expectN("/dev/sds", "xvds") 1183 expectN("/dev/sdt", "xvdt") 1184 expectN("/dev/sdu", "xvdu") 1185 expectN("/dev/sdv", "xvdv") 1186 expectN("/dev/sdw", "xvdw") 1187 expectN("/dev/sdx", "xvdx") 1188 expectN("/dev/sdy", "xvdy") 1189 expectN("/dev/sdz", "xvdz") 1190 expectErr("too many EBS volumes to attach") 1191 } 1192 1193 func (*blockDeviceMappingSuite) TestGetBlockDeviceMappings(c *gc.C) { 1194 mapping, err := ec2.GetBlockDeviceMappings(constraints.Value{}, "jammy", false, nil) 1195 c.Assert(err, jc.ErrorIsNil) 1196 c.Assert(mapping, gc.DeepEquals, []types.BlockDeviceMapping{{ 1197 Ebs: &types.EbsBlockDevice{VolumeSize: aws.Int32(8)}, 1198 DeviceName: aws.String("/dev/sda1"), 1199 }, { 1200 VirtualName: aws.String("ephemeral0"), 1201 DeviceName: aws.String("/dev/sdb"), 1202 }, { 1203 VirtualName: aws.String("ephemeral1"), 1204 DeviceName: aws.String("/dev/sdc"), 1205 }, { 1206 VirtualName: aws.String("ephemeral2"), 1207 DeviceName: aws.String("/dev/sdd"), 1208 }, { 1209 VirtualName: aws.String("ephemeral3"), 1210 DeviceName: aws.String("/dev/sde"), 1211 }}) 1212 } 1213 1214 func (*blockDeviceMappingSuite) TestGetBlockDeviceMappingsController(c *gc.C) { 1215 mapping, err := ec2.GetBlockDeviceMappings(constraints.Value{}, "jammy", true, nil) 1216 c.Assert(err, jc.ErrorIsNil) 1217 c.Assert(mapping, gc.DeepEquals, []types.BlockDeviceMapping{{ 1218 Ebs: &types.EbsBlockDevice{VolumeSize: aws.Int32(32)}, 1219 DeviceName: aws.String("/dev/sda1"), 1220 }, { 1221 VirtualName: aws.String("ephemeral0"), 1222 DeviceName: aws.String("/dev/sdb"), 1223 }, { 1224 VirtualName: aws.String("ephemeral1"), 1225 DeviceName: aws.String("/dev/sdc"), 1226 }, { 1227 VirtualName: aws.String("ephemeral2"), 1228 DeviceName: aws.String("/dev/sdd"), 1229 }, { 1230 VirtualName: aws.String("ephemeral3"), 1231 DeviceName: aws.String("/dev/sde"), 1232 }}) 1233 } 1234 1235 type tagInfo struct { 1236 key string 1237 value string 1238 } 1239 1240 func compareTags(c *gc.C, obtained []types.Tag, expected []tagInfo) { 1241 got := make([]tagInfo, len(obtained)) 1242 for i, t := range obtained { 1243 got[i] = tagInfo{ 1244 key: aws.ToString(t.Key), 1245 value: aws.ToString(t.Value), 1246 } 1247 } 1248 c.Assert(got, jc.SameContents, expected) 1249 }