github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/azure/storage_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure_test 5 6 import ( 7 stdcontext "context" 8 "fmt" 9 "net/http" 10 11 "github.com/Azure/azure-sdk-for-go/sdk/azcore" 12 "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" 13 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" 14 "github.com/juju/errors" 15 "github.com/juju/names/v5" 16 jc "github.com/juju/testing/checkers" 17 "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" 18 gc "gopkg.in/check.v1" 19 20 "github.com/juju/juju/core/instance" 21 "github.com/juju/juju/environs/context" 22 "github.com/juju/juju/provider/azure" 23 "github.com/juju/juju/provider/azure/internal/azuretesting" 24 "github.com/juju/juju/storage" 25 "github.com/juju/juju/testing" 26 ) 27 28 type storageSuite struct { 29 testing.BaseSuite 30 31 provider storage.Provider 32 requests []*http.Request 33 sender azuretesting.Senders 34 35 cloudCallCtx *context.CloudCallContext 36 invalidCredential bool 37 } 38 39 var _ = gc.Suite(&storageSuite{}) 40 41 func (s *storageSuite) SetUpTest(c *gc.C) { 42 s.BaseSuite.SetUpTest(c) 43 s.requests = nil 44 envProvider := newProvider(c, azure.ProviderConfig{ 45 Sender: &s.sender, 46 RequestInspector: &azuretesting.RequestRecorderPolicy{Requests: &s.requests}, 47 CreateTokenCredential: func(appId, appPassword, tenantID string, opts azcore.ClientOptions) (azcore.TokenCredential, error) { 48 return &azuretesting.FakeCredential{}, nil 49 }, 50 }) 51 s.sender = nil 52 53 var err error 54 env := openEnviron(c, envProvider, &s.sender) 55 s.provider, err = env.StorageProvider("azure") 56 c.Assert(err, jc.ErrorIsNil) 57 s.cloudCallCtx = &context.CloudCallContext{ 58 Context: stdcontext.TODO(), 59 InvalidateCredentialFunc: func(string) error { 60 s.invalidCredential = true 61 return nil 62 }, 63 } 64 } 65 66 func (s *storageSuite) TearDownTest(c *gc.C) { 67 s.invalidCredential = false 68 s.BaseSuite.TearDownTest(c) 69 } 70 71 func (s *storageSuite) volumeSource(c *gc.C, attrs ...testing.Attrs) storage.VolumeSource { 72 storageConfig, err := storage.NewConfig("azure", "azure", nil) 73 c.Assert(err, jc.ErrorIsNil) 74 75 s.sender = azuretesting.Senders{} 76 volumeSource, err := s.provider.VolumeSource(storageConfig) 77 c.Assert(err, jc.ErrorIsNil) 78 return volumeSource 79 } 80 81 func (s *storageSuite) TestVolumeSource(c *gc.C) { 82 vs := s.volumeSource(c) 83 c.Assert(vs, gc.NotNil) 84 } 85 86 func (s *storageSuite) TestFilesystemSource(c *gc.C) { 87 storageConfig, err := storage.NewConfig("azure", "azure", nil) 88 c.Assert(err, jc.ErrorIsNil) 89 90 _, err = s.provider.FilesystemSource(storageConfig) 91 c.Assert(err, gc.ErrorMatches, "filesystems not supported") 92 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 93 } 94 95 func (s *storageSuite) TestSupports(c *gc.C) { 96 c.Assert(s.provider.Supports(storage.StorageKindBlock), jc.IsTrue) 97 c.Assert(s.provider.Supports(storage.StorageKindFilesystem), jc.IsFalse) 98 } 99 100 func (s *storageSuite) TestDynamic(c *gc.C) { 101 c.Assert(s.provider.Dynamic(), jc.IsTrue) 102 } 103 104 func (s *storageSuite) TestScope(c *gc.C) { 105 c.Assert(s.provider.Scope(), gc.Equals, storage.ScopeEnviron) 106 } 107 108 func (s *storageSuite) TestCreateVolumes(c *gc.C) { 109 makeVolumeParams := func(volume, machine string, size uint64) storage.VolumeParams { 110 return storage.VolumeParams{ 111 Tag: names.NewVolumeTag(volume), 112 Size: size, 113 Provider: "azure", 114 ResourceTags: map[string]string{"foo": "bar"}, 115 Attachment: &storage.VolumeAttachmentParams{ 116 AttachmentParams: storage.AttachmentParams{ 117 Provider: "azure", 118 Machine: names.NewMachineTag(machine), 119 InstanceId: instance.Id("machine-" + machine), 120 }, 121 Volume: names.NewVolumeTag(volume), 122 }, 123 } 124 } 125 params := []storage.VolumeParams{ 126 makeVolumeParams("0", "0", 1), 127 makeVolumeParams("1", "1", 1025), 128 makeVolumeParams("2", "0", 1024), 129 } 130 131 makeSender := func(name string, sizeGB int32) *azuretesting.MockSender { 132 sender := azuretesting.NewSenderWithValue(&armcompute.Disk{ 133 Name: to.Ptr(name), 134 Properties: &armcompute.DiskProperties{ 135 DiskSizeGB: to.Ptr(sizeGB), 136 }, 137 }) 138 sender.PathPattern = `.*/Microsoft\.Compute/disks/` + name 139 return sender 140 } 141 142 volumeSource := s.volumeSource(c) 143 s.requests = nil 144 s.sender = azuretesting.Senders{ 145 makeSender("volume-0", 32), 146 makeSender("volume-1", 2), 147 makeSender("volume-2", 1), 148 } 149 150 results, err := volumeSource.CreateVolumes(s.cloudCallCtx, params) 151 c.Assert(err, jc.ErrorIsNil) 152 c.Assert(results, gc.HasLen, len(params)) 153 c.Check(results[0].Error, jc.ErrorIsNil) 154 c.Check(results[1].Error, jc.ErrorIsNil) 155 c.Check(results[2].Error, jc.ErrorIsNil) 156 157 // Attachments are deferred. 158 c.Check(results[0].VolumeAttachment, gc.IsNil) 159 c.Check(results[1].VolumeAttachment, gc.IsNil) 160 c.Check(results[2].VolumeAttachment, gc.IsNil) 161 162 makeVolume := func(id string, size uint64) *storage.Volume { 163 return &storage.Volume{ 164 Tag: names.NewVolumeTag(id), 165 VolumeInfo: storage.VolumeInfo{ 166 Size: size, 167 VolumeId: "volume-" + id, 168 Persistent: true, 169 }, 170 } 171 } 172 c.Check(results[0].Volume, jc.DeepEquals, makeVolume("0", 32*1024)) 173 c.Check(results[1].Volume, jc.DeepEquals, makeVolume("1", 2*1024)) 174 c.Check(results[2].Volume, jc.DeepEquals, makeVolume("2", 1*1024)) 175 176 // Validate HTTP request bodies. 177 c.Assert(s.requests, gc.HasLen, 3) 178 c.Assert(s.requests[0].Method, gc.Equals, "PUT") // create volume-0 179 c.Assert(s.requests[1].Method, gc.Equals, "PUT") // create volume-1 180 c.Assert(s.requests[2].Method, gc.Equals, "PUT") // create volume-2 181 182 makeDisk := func(name string, size int32) *armcompute.Disk { 183 tags := map[string]*string{ 184 "foo": to.Ptr("bar"), 185 } 186 return &armcompute.Disk{ 187 Name: to.Ptr(name), 188 Location: to.Ptr("westus"), 189 Tags: tags, 190 SKU: &armcompute.DiskSKU{ 191 Name: to.Ptr(armcompute.DiskStorageAccountTypesStandardLRS), 192 }, 193 Properties: &armcompute.DiskProperties{ 194 DiskSizeGB: to.Ptr(size), 195 CreationData: &armcompute.CreationData{ 196 CreateOption: to.Ptr(armcompute.DiskCreateOptionEmpty), 197 }, 198 }, 199 } 200 } 201 // Only check the PUT requests. 202 assertRequestBody(c, s.requests[0], makeDisk("volume-0", 1)) 203 assertRequestBody(c, s.requests[1], makeDisk("volume-1", 2)) 204 assertRequestBody(c, s.requests[2], makeDisk("volume-2", 1)) 205 } 206 207 func (s *storageSuite) createSenderWithUnauthorisedStatusCode() { 208 unauthSender := &azuretesting.MockSender{} 209 unauthSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) 210 s.sender = azuretesting.Senders{unauthSender, unauthSender, unauthSender} 211 } 212 213 func (s *storageSuite) TestCreateVolumesWithInvalidCredential(c *gc.C) { 214 makeVolumeParams := func(volume, machine string, size uint64) storage.VolumeParams { 215 return storage.VolumeParams{ 216 Tag: names.NewVolumeTag(volume), 217 Size: size, 218 Provider: "azure", 219 ResourceTags: map[string]string{"foo": "bar"}, 220 Attachment: &storage.VolumeAttachmentParams{ 221 AttachmentParams: storage.AttachmentParams{ 222 Provider: "azure", 223 Machine: names.NewMachineTag(machine), 224 InstanceId: instance.Id("machine-" + machine), 225 }, 226 Volume: names.NewVolumeTag(volume), 227 }, 228 } 229 } 230 params := []storage.VolumeParams{ 231 makeVolumeParams("0", "0", 1), 232 makeVolumeParams("1", "1", 1025), 233 makeVolumeParams("2", "0", 1024), 234 } 235 236 volumeSource := s.volumeSource(c) 237 s.requests = nil 238 s.createSenderWithUnauthorisedStatusCode() 239 240 c.Assert(s.invalidCredential, jc.IsFalse) 241 results, err := volumeSource.CreateVolumes(s.cloudCallCtx, params) 242 c.Assert(err, jc.ErrorIsNil) 243 c.Assert(results, gc.HasLen, len(params)) 244 c.Check(results[0].Error, gc.NotNil) 245 c.Check(results[1].Error, gc.NotNil) 246 c.Check(results[2].Error, gc.NotNil) 247 248 // Attachments are deferred. 249 c.Check(results[0].VolumeAttachment, gc.IsNil) 250 c.Check(results[1].VolumeAttachment, gc.IsNil) 251 c.Check(results[2].VolumeAttachment, gc.IsNil) 252 c.Assert(s.invalidCredential, jc.IsTrue) 253 254 // Validate HTTP request bodies. 255 // The authorised workflow attempts to refresh to token so 256 // there's additional requests to account for as well. 257 c.Assert(s.requests, gc.HasLen, 3) 258 c.Assert(s.requests[0].Method, gc.Equals, "PUT") // create volume-0 259 c.Assert(s.requests[1].Method, gc.Equals, "PUT") // create volume-1 260 c.Assert(s.requests[2].Method, gc.Equals, "PUT") // create volume-2 261 262 makeDisk := func(name string, size int32) *armcompute.Disk { 263 tags := map[string]*string{ 264 "foo": to.Ptr("bar"), 265 } 266 return &armcompute.Disk{ 267 Name: to.Ptr(name), 268 Location: to.Ptr("westus"), 269 Tags: tags, 270 Properties: &armcompute.DiskProperties{ 271 DiskSizeGB: to.Ptr(size), 272 CreationData: &armcompute.CreationData{ 273 CreateOption: to.Ptr(armcompute.DiskCreateOptionEmpty), 274 }, 275 }, 276 SKU: &armcompute.DiskSKU{ 277 Name: to.Ptr(armcompute.DiskStorageAccountTypesStandardLRS), 278 }, 279 } 280 } 281 assertRequestBody(c, s.requests[0], makeDisk("volume-0", 1)) 282 assertRequestBody(c, s.requests[1], makeDisk("volume-1", 2)) 283 assertRequestBody(c, s.requests[2], makeDisk("volume-2", 1)) 284 } 285 286 func (s *storageSuite) TestListVolumes(c *gc.C) { 287 volumeSource := s.volumeSource(c) 288 disks := []*armcompute.Disk{{ 289 Name: to.Ptr("volume-0"), 290 }, { 291 Name: to.Ptr("machine-0"), 292 }, { 293 Name: to.Ptr("volume-1"), 294 }} 295 volumeSender := azuretesting.NewSenderWithValue(armcompute.DiskList{ 296 Value: disks, 297 }) 298 volumeSender.PathPattern = `.*/Microsoft\.Compute/disks` 299 s.sender = azuretesting.Senders{volumeSender} 300 301 volumeIds, err := volumeSource.ListVolumes(s.cloudCallCtx) 302 c.Assert(err, jc.ErrorIsNil) 303 c.Assert(volumeIds, jc.SameContents, []string{"volume-0", "volume-1"}) 304 } 305 306 func (s *storageSuite) TestListVolumesWithInvalidCredential(c *gc.C) { 307 volumeSource := s.volumeSource(c) 308 s.createSenderWithUnauthorisedStatusCode() 309 310 c.Assert(s.invalidCredential, jc.IsFalse) 311 _, err := volumeSource.ListVolumes(s.cloudCallCtx) 312 c.Assert(err, gc.NotNil) 313 c.Assert(s.invalidCredential, jc.IsTrue) 314 } 315 316 func (s *storageSuite) TestListVolumesErrors(c *gc.C) { 317 volumeSource := s.volumeSource(c) 318 sender := &azuretesting.MockSender{} 319 sender.SetAndRepeatError(errors.New("no disks for you"), -1) 320 s.sender = azuretesting.Senders{ 321 sender, 322 sender, // for the retry attempt 323 } 324 _, err := volumeSource.ListVolumes(s.cloudCallCtx) 325 c.Assert(err, gc.ErrorMatches, "listing disks: no disks for you") 326 } 327 328 func (s *storageSuite) TestDescribeVolumes(c *gc.C) { 329 volumeSource := s.volumeSource(c) 330 volumeSender := azuretesting.NewSenderWithValue(&armcompute.Disk{ 331 Properties: &armcompute.DiskProperties{ 332 DiskSizeGB: to.Ptr(int32(1024)), 333 }, 334 }) 335 volumeSender.PathPattern = `.*/Microsoft\.Compute/disks/volume-0` 336 s.sender = azuretesting.Senders{volumeSender} 337 338 results, err := volumeSource.DescribeVolumes(s.cloudCallCtx, []string{"volume-0"}) 339 c.Assert(err, jc.ErrorIsNil) 340 c.Assert(results, jc.DeepEquals, []storage.DescribeVolumesResult{{ 341 VolumeInfo: &storage.VolumeInfo{ 342 VolumeId: "volume-0", 343 Size: 1024 * 1024, 344 Persistent: true, 345 }, 346 }}) 347 } 348 349 func (s *storageSuite) TestDescribeVolumesWithInvalidCredential(c *gc.C) { 350 volumeSource := s.volumeSource(c) 351 s.createSenderWithUnauthorisedStatusCode() 352 353 c.Assert(s.invalidCredential, jc.IsFalse) 354 _, err := volumeSource.DescribeVolumes(s.cloudCallCtx, []string{"volume-0"}) 355 c.Assert(err, jc.ErrorIsNil) 356 results, err := volumeSource.DescribeVolumes(s.cloudCallCtx, []string{"volume-0"}) 357 c.Assert(err, jc.ErrorIsNil) 358 c.Assert(results[0].Error, gc.NotNil) 359 c.Assert(s.invalidCredential, jc.IsTrue) 360 } 361 362 func (s *storageSuite) TestDescribeVolumesNotFound(c *gc.C) { 363 volumeSource := s.volumeSource(c) 364 volumeSender := &azuretesting.MockSender{} 365 response := azuretesting.NewResponseWithBodyAndStatus( 366 azuretesting.NewBody("{}"), 367 http.StatusNotFound, 368 "disk not found", 369 ) 370 volumeSender.AppendResponse(response) 371 s.sender = azuretesting.Senders{volumeSender} 372 results, err := volumeSource.DescribeVolumes(s.cloudCallCtx, []string{"volume-42"}) 373 c.Assert(err, jc.ErrorIsNil) 374 c.Assert(results, gc.HasLen, 1) 375 c.Assert(results[0].Error, jc.Satisfies, errors.IsNotFound) 376 c.Assert(results[0].Error, gc.ErrorMatches, `disk volume-42 not found`) 377 } 378 379 func (s *storageSuite) TestDestroyVolumes(c *gc.C) { 380 volumeSource := s.volumeSource(c) 381 382 volume0Sender := azuretesting.NewSenderWithValue(&odataerrors.ODataError{}) 383 volume0Sender.PathPattern = `.*/Microsoft\.Compute/disks/volume-0` 384 s.sender = azuretesting.Senders{volume0Sender} 385 386 results, err := volumeSource.DestroyVolumes(s.cloudCallCtx, []string{"volume-0"}) 387 c.Assert(err, jc.ErrorIsNil) 388 c.Assert(results, gc.HasLen, 1) 389 c.Assert(results[0], jc.ErrorIsNil) 390 } 391 392 func (s *storageSuite) TestDestroyVolumesWithInvalidCredential(c *gc.C) { 393 volumeSource := s.volumeSource(c) 394 395 s.createSenderWithUnauthorisedStatusCode() 396 c.Assert(s.invalidCredential, jc.IsFalse) 397 results, err := volumeSource.DestroyVolumes(s.cloudCallCtx, []string{"volume-0"}) 398 c.Assert(err, jc.ErrorIsNil) 399 c.Assert(results, gc.HasLen, 1) 400 c.Assert(results[0], gc.NotNil) 401 c.Assert(s.invalidCredential, jc.IsTrue) 402 } 403 404 func (s *storageSuite) TestDestroyVolumesNotFound(c *gc.C) { 405 volumeSource := s.volumeSource(c) 406 407 volume42Sender := &azuretesting.MockSender{} 408 volume42Sender.AppendResponse(azuretesting.NewResponseWithStatus( 409 "disk not found", http.StatusNotFound, 410 )) 411 s.sender = azuretesting.Senders{volume42Sender} 412 413 results, err := volumeSource.DestroyVolumes(s.cloudCallCtx, []string{"volume-42"}) 414 c.Assert(err, jc.ErrorIsNil) 415 c.Assert(results, gc.HasLen, 1) 416 c.Assert(results[0], jc.ErrorIsNil) 417 } 418 419 func (s *storageSuite) TestAttachVolumes(c *gc.C) { 420 // machine-1 has a single data disk with LUN 0. 421 machine1DataDisks := []*armcompute.DataDisk{{ 422 Lun: to.Ptr(int32(0)), 423 Name: to.Ptr("volume-1"), 424 }} 425 // machine-2 has 32 data disks; no LUNs free. 426 machine2DataDisks := make([]*armcompute.DataDisk, 32) 427 for i := range machine2DataDisks { 428 machine2DataDisks[i] = &armcompute.DataDisk{ 429 Lun: to.Ptr(int32(i)), 430 Name: to.Ptr(fmt.Sprintf("volume-%d", i)), 431 } 432 } 433 434 // volume-0 and volume-2 are attached to machine-0 435 // volume-1 is attached to machine-1 436 // volume-3 is attached to machine-42, but machine-42 is missing 437 // volume-42 is attached to machine-2, but machine-2 has no free LUNs 438 makeParams := func(volume, machine string, size uint64) storage.VolumeAttachmentParams { 439 return storage.VolumeAttachmentParams{ 440 AttachmentParams: storage.AttachmentParams{ 441 Provider: "azure", 442 Machine: names.NewMachineTag(machine), 443 InstanceId: instance.Id("machine-" + machine), 444 }, 445 Volume: names.NewVolumeTag(volume), 446 VolumeId: "volume-" + volume, 447 } 448 } 449 params := []storage.VolumeAttachmentParams{ 450 makeParams("0", "0", 1), 451 makeParams("1", "1", 1025), 452 makeParams("2", "0", 1024), 453 makeParams("3", "42", 40), 454 makeParams("42", "2", 50), 455 } 456 457 virtualMachines := []*armcompute.VirtualMachine{{ 458 Name: to.Ptr("machine-0"), 459 Properties: &armcompute.VirtualMachineProperties{ 460 StorageProfile: &armcompute.StorageProfile{}, 461 }, 462 }, { 463 Name: to.Ptr("machine-1"), 464 Properties: &armcompute.VirtualMachineProperties{ 465 StorageProfile: &armcompute.StorageProfile{DataDisks: machine1DataDisks}, 466 }, 467 }, { 468 Name: to.Ptr("machine-2"), 469 Properties: &armcompute.VirtualMachineProperties{ 470 StorageProfile: &armcompute.StorageProfile{DataDisks: machine2DataDisks}, 471 }, 472 }} 473 474 // There should be a one API calls to list VMs, and one update per modified instance. 475 virtualMachinesSender := azuretesting.NewSenderWithValue(armcompute.VirtualMachineListResult{ 476 Value: virtualMachines, 477 }) 478 virtualMachinesSender.PathPattern = `.*/Microsoft\.Compute/virtualMachines` 479 updateVirtualMachine0Sender := azuretesting.NewSenderWithValue(&armcompute.VirtualMachine{}) 480 updateVirtualMachine0Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-0` 481 482 volumeSource := s.volumeSource(c) 483 s.requests = nil 484 s.sender = azuretesting.Senders{ 485 virtualMachinesSender, 486 updateVirtualMachine0Sender, 487 updateVirtualMachine0Sender, 488 } 489 490 results, err := volumeSource.AttachVolumes(s.cloudCallCtx, params) 491 c.Assert(err, jc.ErrorIsNil) 492 c.Assert(results, gc.HasLen, len(params)) 493 494 c.Check(results[0].Error, jc.ErrorIsNil) 495 c.Check(results[1].Error, jc.ErrorIsNil) 496 c.Check(results[2].Error, jc.ErrorIsNil) 497 c.Check(results[3].Error, gc.ErrorMatches, "instance machine-42 not found") 498 c.Check(results[4].Error, gc.ErrorMatches, "choosing LUN: all LUNs are in use") 499 500 // Validate HTTP request bodies. 501 c.Assert(s.requests, gc.HasLen, 2) 502 c.Assert(s.requests[0].Method, gc.Equals, "GET") // list virtual machines 503 c.Assert(s.requests[1].Method, gc.Equals, "PUT") // update machine-0 504 505 makeManagedDisk := func(volumeName string) *armcompute.ManagedDiskParameters { 506 return &armcompute.ManagedDiskParameters{ 507 ID: to.Ptr(fmt.Sprintf("/subscriptions/%s/resourceGroups/juju-testmodel-deadbeef/providers/Microsoft.Compute/disks/%s", fakeManagedSubscriptionId, volumeName)), 508 } 509 } 510 511 machine0DataDisks := []*armcompute.DataDisk{{ 512 Lun: to.Ptr(int32(0)), 513 Name: to.Ptr("volume-0"), 514 ManagedDisk: makeManagedDisk("volume-0"), 515 Caching: to.Ptr(armcompute.CachingTypesReadWrite), 516 CreateOption: to.Ptr(armcompute.DiskCreateOptionTypesAttach), 517 }, { 518 Lun: to.Ptr(int32(1)), 519 Name: to.Ptr("volume-2"), 520 ManagedDisk: makeManagedDisk("volume-2"), 521 Caching: to.Ptr(armcompute.CachingTypesReadWrite), 522 CreateOption: to.Ptr(armcompute.DiskCreateOptionTypesAttach), 523 }} 524 525 assertRequestBody(c, s.requests[1], &armcompute.VirtualMachine{ 526 Name: to.Ptr("machine-0"), 527 Properties: &armcompute.VirtualMachineProperties{ 528 StorageProfile: &armcompute.StorageProfile{ 529 DataDisks: machine0DataDisks, 530 }, 531 }, 532 }) 533 } 534 535 func (s *storageSuite) TestDetachVolumes(c *gc.C) { 536 // machine-0 has a three data disks: volume-0, volume-1 and volume-2 537 machine0DataDisks := []*armcompute.DataDisk{{ 538 Lun: to.Ptr(int32(0)), 539 Name: to.Ptr("volume-0"), 540 }, { 541 Lun: to.Ptr(int32(1)), 542 Name: to.Ptr("volume-1"), 543 }, { 544 Lun: to.Ptr(int32(2)), 545 Name: to.Ptr("volume-2"), 546 }} 547 548 makeParams := func(volume, machine string) storage.VolumeAttachmentParams { 549 return storage.VolumeAttachmentParams{ 550 AttachmentParams: storage.AttachmentParams{ 551 Provider: "azure", 552 Machine: names.NewMachineTag(machine), 553 InstanceId: instance.Id("machine-" + machine), 554 }, 555 Volume: names.NewVolumeTag(volume), 556 VolumeId: "volume-" + volume, 557 } 558 } 559 params := []storage.VolumeAttachmentParams{ 560 makeParams("1", "0"), 561 makeParams("1", "0"), 562 makeParams("42", "1"), 563 makeParams("2", "42"), 564 } 565 566 virtualMachines := []*armcompute.VirtualMachine{{ 567 Name: to.Ptr("machine-0"), 568 Properties: &armcompute.VirtualMachineProperties{ 569 StorageProfile: &armcompute.StorageProfile{DataDisks: machine0DataDisks}, 570 }, 571 }, { 572 Name: to.Ptr("machine-1"), 573 Properties: &armcompute.VirtualMachineProperties{ 574 StorageProfile: &armcompute.StorageProfile{}, 575 }, 576 }} 577 578 // There should be a one API calls to list VMs, and one update per modified instance. 579 virtualMachinesSender := azuretesting.NewSenderWithValue(armcompute.VirtualMachineListResult{ 580 Value: virtualMachines, 581 }) 582 virtualMachinesSender.PathPattern = `.*/Microsoft\.Compute/virtualMachines` 583 updateVirtualMachine0Sender := azuretesting.NewSenderWithValue(&armcompute.VirtualMachine{}) 584 updateVirtualMachine0Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-0` 585 586 volumeSource := s.volumeSource(c) 587 s.requests = nil 588 s.sender = azuretesting.Senders{ 589 virtualMachinesSender, 590 updateVirtualMachine0Sender, 591 updateVirtualMachine0Sender, 592 } 593 594 results, err := volumeSource.DetachVolumes(s.cloudCallCtx, params) 595 c.Assert(err, jc.ErrorIsNil) 596 c.Assert(results, gc.HasLen, len(params)) 597 598 c.Check(results[0], jc.ErrorIsNil) 599 c.Check(results[1], jc.ErrorIsNil) 600 c.Check(results[2], jc.ErrorIsNil) 601 c.Check(results[3], gc.ErrorMatches, "instance machine-42 not found") 602 603 // Validate HTTP request bodies. 604 c.Assert(s.requests, gc.HasLen, 2) 605 c.Assert(s.requests[0].Method, gc.Equals, "GET") // list virtual machines 606 c.Assert(s.requests[1].Method, gc.Equals, "PUT") // update machine-0 607 608 assertRequestBody(c, s.requests[1], &armcompute.VirtualMachine{ 609 Name: to.Ptr("machine-0"), 610 Properties: &armcompute.VirtualMachineProperties{ 611 StorageProfile: &armcompute.StorageProfile{ 612 DataDisks: []*armcompute.DataDisk{ 613 machine0DataDisks[0], 614 machine0DataDisks[2], 615 }, 616 }, 617 }, 618 }) 619 } 620 621 func (s *storageSuite) TestDetachVolumesFinal(c *gc.C) { 622 // machine-0 has a one data disk: volume-0. 623 machine0DataDisks := []*armcompute.DataDisk{{ 624 Lun: to.Ptr(int32(0)), 625 Name: to.Ptr("volume-0"), 626 }} 627 628 params := []storage.VolumeAttachmentParams{{ 629 AttachmentParams: storage.AttachmentParams{ 630 Provider: "azure", 631 Machine: names.NewMachineTag("0"), 632 InstanceId: instance.Id("machine-0"), 633 }, 634 Volume: names.NewVolumeTag("0"), 635 VolumeId: "volume-0", 636 }} 637 638 virtualMachines := []*armcompute.VirtualMachine{{ 639 Name: to.Ptr("machine-0"), 640 Properties: &armcompute.VirtualMachineProperties{ 641 StorageProfile: &armcompute.StorageProfile{DataDisks: machine0DataDisks}, 642 }, 643 }} 644 645 // There should be a one API call to list VMs, and one update to the VM. 646 virtualMachinesSender := azuretesting.NewSenderWithValue(armcompute.VirtualMachineListResult{ 647 Value: virtualMachines, 648 }) 649 virtualMachinesSender.PathPattern = `.*/Microsoft\.Compute/virtualMachines` 650 updateVirtualMachine0Sender := azuretesting.NewSenderWithValue(&armcompute.VirtualMachine{}) 651 updateVirtualMachine0Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-0` 652 653 volumeSource := s.volumeSource(c) 654 s.requests = nil 655 s.sender = azuretesting.Senders{ 656 virtualMachinesSender, 657 updateVirtualMachine0Sender, 658 } 659 660 results, err := volumeSource.DetachVolumes(s.cloudCallCtx, params) 661 c.Assert(err, jc.ErrorIsNil) 662 c.Assert(results, gc.HasLen, len(params)) 663 c.Assert(results[0], jc.ErrorIsNil) 664 665 // Validate HTTP request bodies. 666 c.Assert(s.requests, gc.HasLen, 2) 667 c.Assert(s.requests[0].Method, gc.Equals, "GET") // list virtual machines 668 c.Assert(s.requests[1].Method, gc.Equals, "PUT") // update machine-0 669 670 assertRequestBody(c, s.requests[1], &armcompute.VirtualMachine{ 671 Name: to.Ptr("machine-0"), 672 Properties: &armcompute.VirtualMachineProperties{ 673 StorageProfile: &armcompute.StorageProfile{ 674 DataDisks: []*armcompute.DataDisk{}, 675 }, 676 }, 677 }) 678 }