github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "fmt" 8 "net/http" 9 10 "github.com/Azure/azure-sdk-for-go/arm/compute" 11 armstorage "github.com/Azure/azure-sdk-for-go/arm/storage" 12 azurestorage "github.com/Azure/azure-sdk-for-go/storage" 13 "github.com/Azure/go-autorest/autorest/to" 14 "github.com/juju/errors" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/names.v2" 18 19 "github.com/juju/juju/instance" 20 "github.com/juju/juju/provider/azure" 21 "github.com/juju/juju/provider/azure/internal/azureauth" 22 "github.com/juju/juju/provider/azure/internal/azuretesting" 23 "github.com/juju/juju/storage" 24 "github.com/juju/juju/testing" 25 ) 26 27 type storageSuite struct { 28 testing.BaseSuite 29 30 storageClient azuretesting.MockStorageClient 31 provider storage.Provider 32 requests []*http.Request 33 sender azuretesting.Senders 34 } 35 36 var _ = gc.Suite(&storageSuite{}) 37 38 func (s *storageSuite) SetUpTest(c *gc.C) { 39 s.BaseSuite.SetUpTest(c) 40 s.storageClient = azuretesting.MockStorageClient{} 41 s.requests = nil 42 envProvider := newProvider(c, azure.ProviderConfig{ 43 Sender: &s.sender, 44 NewStorageClient: s.storageClient.NewClient, 45 RequestInspector: azuretesting.RequestRecorder(&s.requests), 46 RandomWindowsAdminPassword: func() string { return "sorandom" }, 47 InteractiveCreateServicePrincipal: azureauth.InteractiveCreateServicePrincipal, 48 }) 49 s.sender = nil 50 51 var err error 52 env := openEnviron(c, envProvider, &s.sender) 53 s.provider, err = env.StorageProvider("azure") 54 c.Assert(err, jc.ErrorIsNil) 55 } 56 57 func (s *storageSuite) volumeSource(c *gc.C, attrs ...testing.Attrs) storage.VolumeSource { 58 storageConfig, err := storage.NewConfig("azure", "azure", nil) 59 c.Assert(err, jc.ErrorIsNil) 60 61 volumeSource, err := s.provider.VolumeSource(storageConfig) 62 c.Assert(err, jc.ErrorIsNil) 63 64 // Force an explicit refresh of the access token, so it isn't done 65 // implicitly during the tests. 66 s.sender = azuretesting.Senders{ 67 tokenRefreshSender(), 68 } 69 err = azure.ForceVolumeSourceTokenRefresh(volumeSource) 70 c.Assert(err, jc.ErrorIsNil) 71 return volumeSource 72 } 73 74 func (s *storageSuite) accountSender() *azuretesting.MockSender { 75 envTags := map[string]*string{ 76 "juju-model-uuid": to.StringPtr(testing.ModelTag.Id()), 77 } 78 account := armstorage.Account{ 79 Name: to.StringPtr(storageAccountName), 80 Type: to.StringPtr("Standard_LRS"), 81 Tags: &envTags, 82 Properties: &armstorage.AccountProperties{ 83 PrimaryEndpoints: &armstorage.Endpoints{ 84 Blob: to.StringPtr(fmt.Sprintf("https://%s.blob.storage.azurestack.local/", storageAccountName)), 85 }, 86 }, 87 } 88 accountSender := azuretesting.NewSenderWithValue(account) 89 accountSender.PathPattern = ".*/storageAccounts/" + storageAccountName + ".*" 90 return accountSender 91 } 92 93 func (s *storageSuite) accountKeysSender() *azuretesting.MockSender { 94 keys := []armstorage.AccountKey{{ 95 KeyName: to.StringPtr(fakeStorageAccountKey + "-name"), 96 Value: to.StringPtr(fakeStorageAccountKey), 97 Permissions: armstorage.FULL, 98 }, { 99 KeyName: to.StringPtr("key2-name"), 100 Value: to.StringPtr("key2"), 101 Permissions: armstorage.FULL, 102 }} 103 result := armstorage.AccountListKeysResult{Keys: &keys} 104 keysSender := azuretesting.NewSenderWithValue(&result) 105 keysSender.PathPattern = ".*/storageAccounts/.*/listKeys" 106 return keysSender 107 } 108 109 func (s *storageSuite) TestVolumeSource(c *gc.C) { 110 vs := s.volumeSource(c) 111 c.Assert(vs, gc.NotNil) 112 } 113 114 func (s *storageSuite) TestFilesystemSource(c *gc.C) { 115 storageConfig, err := storage.NewConfig("azure", "azure", nil) 116 c.Assert(err, jc.ErrorIsNil) 117 118 _, err = s.provider.FilesystemSource(storageConfig) 119 c.Assert(err, gc.ErrorMatches, "filesystems not supported") 120 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 121 } 122 123 func (s *storageSuite) TestSupports(c *gc.C) { 124 c.Assert(s.provider.Supports(storage.StorageKindBlock), jc.IsTrue) 125 c.Assert(s.provider.Supports(storage.StorageKindFilesystem), jc.IsFalse) 126 } 127 128 func (s *storageSuite) TestDynamic(c *gc.C) { 129 c.Assert(s.provider.Dynamic(), jc.IsTrue) 130 } 131 132 func (s *storageSuite) TestScope(c *gc.C) { 133 c.Assert(s.provider.Scope(), gc.Equals, storage.ScopeEnviron) 134 } 135 136 func (s *storageSuite) TestCreateVolumes(c *gc.C) { 137 // machine-1 has a single data disk with LUN 0. 138 machine1DataDisks := []compute.DataDisk{{Lun: to.Int32Ptr(0)}} 139 // machine-2 has 32 data disks; no LUNs free. 140 machine2DataDisks := make([]compute.DataDisk, 32) 141 for i := range machine2DataDisks { 142 machine2DataDisks[i].Lun = to.Int32Ptr(int32(i)) 143 } 144 145 // volume-0 and volume-2 are attached to machine-0 146 // volume-1 is attached to machine-1 147 // volume-3 is attached to machine-42, but machine-42 is missing 148 // volume-42 is attached to machine-2, but machine-2 has no free LUNs 149 makeVolumeParams := func(volume, machine string, size uint64) storage.VolumeParams { 150 return storage.VolumeParams{ 151 Tag: names.NewVolumeTag(volume), 152 Size: size, 153 Provider: "azure", 154 Attachment: &storage.VolumeAttachmentParams{ 155 AttachmentParams: storage.AttachmentParams{ 156 Provider: "azure", 157 Machine: names.NewMachineTag(machine), 158 InstanceId: instance.Id("machine-" + machine), 159 }, 160 Volume: names.NewVolumeTag(volume), 161 }, 162 } 163 } 164 params := []storage.VolumeParams{ 165 makeVolumeParams("0", "0", 1), 166 makeVolumeParams("1", "1", 1025), 167 makeVolumeParams("2", "0", 1024), 168 makeVolumeParams("3", "42", 40), 169 makeVolumeParams("42", "2", 50), 170 } 171 172 virtualMachines := []compute.VirtualMachine{{ 173 Name: to.StringPtr("machine-0"), 174 Properties: &compute.VirtualMachineProperties{ 175 StorageProfile: &compute.StorageProfile{}, 176 }, 177 }, { 178 Name: to.StringPtr("machine-1"), 179 Properties: &compute.VirtualMachineProperties{ 180 StorageProfile: &compute.StorageProfile{DataDisks: &machine1DataDisks}, 181 }, 182 }, { 183 Name: to.StringPtr("machine-2"), 184 Properties: &compute.VirtualMachineProperties{ 185 StorageProfile: &compute.StorageProfile{DataDisks: &machine2DataDisks}, 186 }, 187 }} 188 189 // There should be a one API calls to list VMs, and one update per modified instance. 190 virtualMachinesSender := azuretesting.NewSenderWithValue(compute.VirtualMachineListResult{ 191 Value: &virtualMachines, 192 }) 193 virtualMachinesSender.PathPattern = `.*/Microsoft\.Compute/virtualMachines` 194 updateVirtualMachine0Sender := azuretesting.NewSenderWithValue(&compute.VirtualMachine{}) 195 updateVirtualMachine0Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-0` 196 updateVirtualMachine1Sender := azuretesting.NewSenderWithValue(&compute.VirtualMachine{}) 197 updateVirtualMachine1Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-1` 198 volumeSource := s.volumeSource(c) 199 s.sender = azuretesting.Senders{ 200 virtualMachinesSender, 201 s.accountSender(), 202 updateVirtualMachine0Sender, 203 updateVirtualMachine1Sender, 204 } 205 206 results, err := volumeSource.CreateVolumes(params) 207 c.Assert(err, jc.ErrorIsNil) 208 c.Assert(results, gc.HasLen, len(params)) 209 210 c.Check(results[0].Error, jc.ErrorIsNil) 211 c.Check(results[1].Error, jc.ErrorIsNil) 212 c.Check(results[2].Error, jc.ErrorIsNil) 213 c.Check(results[3].Error, gc.ErrorMatches, "instance machine-42 not found") 214 c.Check(results[4].Error, gc.ErrorMatches, "choosing LUN: all LUNs are in use") 215 216 // Validate HTTP request bodies. 217 c.Assert(s.requests, gc.HasLen, 4) 218 c.Assert(s.requests[0].Method, gc.Equals, "GET") // list virtual machines 219 c.Assert(s.requests[1].Method, gc.Equals, "GET") // list storage accounts 220 c.Assert(s.requests[2].Method, gc.Equals, "PUT") // update machine-0 221 c.Assert(s.requests[3].Method, gc.Equals, "PUT") // update machine-1 222 223 machine0DataDisks := []compute.DataDisk{{ 224 Lun: to.Int32Ptr(0), 225 DiskSizeGB: to.Int32Ptr(1), 226 Name: to.StringPtr("volume-0"), 227 Vhd: &compute.VirtualHardDisk{URI: to.StringPtr(fmt.Sprintf( 228 "https://%s.blob.storage.azurestack.local/datavhds/volume-0.vhd", 229 storageAccountName, 230 ))}, 231 Caching: compute.ReadWrite, 232 CreateOption: compute.Empty, 233 }, { 234 Lun: to.Int32Ptr(1), 235 DiskSizeGB: to.Int32Ptr(1), 236 Name: to.StringPtr("volume-2"), 237 Vhd: &compute.VirtualHardDisk{URI: to.StringPtr(fmt.Sprintf( 238 "https://%s.blob.storage.azurestack.local/datavhds/volume-2.vhd", 239 storageAccountName, 240 ))}, 241 Caching: compute.ReadWrite, 242 CreateOption: compute.Empty, 243 }} 244 virtualMachines[0].Properties.StorageProfile.DataDisks = &machine0DataDisks 245 assertRequestBody(c, s.requests[2], &virtualMachines[0]) 246 247 machine1DataDisks = append(machine1DataDisks, compute.DataDisk{ 248 Lun: to.Int32Ptr(1), 249 DiskSizeGB: to.Int32Ptr(2), 250 Name: to.StringPtr("volume-1"), 251 Vhd: &compute.VirtualHardDisk{URI: to.StringPtr(fmt.Sprintf( 252 "https://%s.blob.storage.azurestack.local/datavhds/volume-1.vhd", 253 storageAccountName, 254 ))}, 255 Caching: compute.ReadWrite, 256 CreateOption: compute.Empty, 257 }) 258 assertRequestBody(c, s.requests[3], &virtualMachines[1]) 259 } 260 261 func (s *storageSuite) TestListVolumes(c *gc.C) { 262 s.storageClient.ListBlobsFunc = func( 263 container string, 264 params azurestorage.ListBlobsParameters, 265 ) (azurestorage.BlobListResponse, error) { 266 return azurestorage.BlobListResponse{ 267 Blobs: []azurestorage.Blob{{ 268 Name: "volume-1.vhd", 269 Properties: azurestorage.BlobProperties{ 270 ContentLength: 1024 * 1024, // 1MiB 271 }, 272 }, { 273 Name: "volume-0.vhd", 274 Properties: azurestorage.BlobProperties{ 275 ContentLength: 1024 * 1024 * 1024 * 1024, // 1TiB 276 }, 277 }, { 278 Name: "junk.vhd", 279 }, { 280 Name: "volume", 281 }}, 282 }, nil 283 } 284 285 volumeSource := s.volumeSource(c) 286 s.sender = azuretesting.Senders{ 287 s.accountSender(), 288 s.accountKeysSender(), 289 } 290 volumeIds, err := volumeSource.ListVolumes() 291 c.Assert(err, jc.ErrorIsNil) 292 s.storageClient.CheckCallNames(c, "NewClient", "ListBlobs") 293 s.storageClient.CheckCall( 294 c, 0, "NewClient", storageAccountName, fakeStorageAccountKey, 295 "storage.azurestack.local", azurestorage.DefaultAPIVersion, true, 296 ) 297 s.storageClient.CheckCall(c, 1, "ListBlobs", "datavhds", azurestorage.ListBlobsParameters{}) 298 c.Assert(volumeIds, jc.DeepEquals, []string{"volume-1", "volume-0"}) 299 } 300 301 func (s *storageSuite) TestListVolumesErrors(c *gc.C) { 302 volumeSource := s.volumeSource(c) 303 s.sender = azuretesting.Senders{ 304 s.accountSender(), 305 s.accountKeysSender(), 306 } 307 308 s.storageClient.SetErrors(errors.New("no client for you")) 309 _, err := volumeSource.ListVolumes() 310 c.Assert(err, gc.ErrorMatches, "listing volumes: getting storage client: no client for you") 311 312 s.storageClient.SetErrors(nil, errors.New("no blobs for you")) 313 _, err = volumeSource.ListVolumes() 314 c.Assert(err, gc.ErrorMatches, "listing volumes: listing blobs: no blobs for you") 315 } 316 317 func (s *storageSuite) TestDescribeVolumes(c *gc.C) { 318 s.storageClient.ListBlobsFunc = func( 319 container string, 320 params azurestorage.ListBlobsParameters, 321 ) (azurestorage.BlobListResponse, error) { 322 return azurestorage.BlobListResponse{ 323 Blobs: []azurestorage.Blob{{ 324 Name: "volume-1.vhd", 325 Properties: azurestorage.BlobProperties{ 326 ContentLength: 1024 * 1024, // 1MiB 327 }, 328 }, { 329 Name: "volume-0.vhd", 330 Properties: azurestorage.BlobProperties{ 331 ContentLength: 1024 * 1024 * 1024 * 1024, // 1TiB 332 }, 333 }}, 334 }, nil 335 } 336 337 volumeSource := s.volumeSource(c) 338 s.sender = azuretesting.Senders{ 339 s.accountSender(), 340 s.accountKeysSender(), 341 } 342 results, err := volumeSource.DescribeVolumes([]string{"volume-0", "volume-1", "volume-0", "volume-42"}) 343 c.Assert(err, jc.ErrorIsNil) 344 s.storageClient.CheckCallNames(c, "NewClient", "ListBlobs") 345 s.storageClient.CheckCall( 346 c, 0, "NewClient", storageAccountName, fakeStorageAccountKey, 347 "storage.azurestack.local", azurestorage.DefaultAPIVersion, true, 348 ) 349 c.Assert(results, gc.HasLen, 4) 350 c.Assert(results[:3], jc.DeepEquals, []storage.DescribeVolumesResult{{ 351 VolumeInfo: &storage.VolumeInfo{ 352 VolumeId: "volume-0", 353 Size: 1024 * 1024, 354 Persistent: true, 355 }, 356 }, { 357 VolumeInfo: &storage.VolumeInfo{ 358 VolumeId: "volume-1", 359 Size: 1, 360 Persistent: true, 361 }, 362 }, { 363 VolumeInfo: &storage.VolumeInfo{ 364 VolumeId: "volume-0", 365 Size: 1024 * 1024, 366 Persistent: true, 367 }, 368 }}) 369 c.Assert(results[3].Error, gc.ErrorMatches, "volume-42 not found") 370 } 371 372 func (s *storageSuite) TestDestroyVolumes(c *gc.C) { 373 volumeSource := s.volumeSource(c) 374 s.sender = azuretesting.Senders{ 375 s.accountSender(), 376 s.accountKeysSender(), 377 } 378 results, err := volumeSource.DestroyVolumes([]string{"volume-0", "volume-42"}) 379 c.Assert(err, jc.ErrorIsNil) 380 c.Assert(results, gc.HasLen, 2) 381 c.Assert(results[0], jc.ErrorIsNil) 382 c.Assert(results[1], jc.ErrorIsNil) 383 s.storageClient.CheckCallNames(c, "NewClient", "DeleteBlobIfExists", "DeleteBlobIfExists") 384 s.storageClient.CheckCall(c, 1, "DeleteBlobIfExists", "datavhds", "volume-0.vhd") 385 s.storageClient.CheckCall(c, 2, "DeleteBlobIfExists", "datavhds", "volume-42.vhd") 386 } 387 388 func (s *storageSuite) TestAttachVolumes(c *gc.C) { 389 // machine-1 has a single data disk with LUN 0. 390 machine1DataDisks := []compute.DataDisk{{ 391 Lun: to.Int32Ptr(0), 392 Name: to.StringPtr("volume-1"), 393 Vhd: &compute.VirtualHardDisk{ 394 URI: to.StringPtr(fmt.Sprintf( 395 "https://%s.blob.storage.azurestack.local/datavhds/volume-1.vhd", 396 storageAccountName, 397 )), 398 }, 399 }} 400 // machine-2 has 32 data disks; no LUNs free. 401 machine2DataDisks := make([]compute.DataDisk, 32) 402 for i := range machine2DataDisks { 403 machine2DataDisks[i].Lun = to.Int32Ptr(int32(i)) 404 machine2DataDisks[i].Name = to.StringPtr(fmt.Sprintf("volume-%d", i)) 405 machine2DataDisks[i].Vhd = &compute.VirtualHardDisk{ 406 URI: to.StringPtr(fmt.Sprintf( 407 "https://%s.blob.storage.azurestack.local/datavhds/volume-%d.vhd", 408 storageAccountName, i, 409 )), 410 } 411 } 412 413 // volume-0 and volume-2 are attached to machine-0 414 // volume-1 is attached to machine-1 415 // volume-3 is attached to machine-42, but machine-42 is missing 416 // volume-42 is attached to machine-2, but machine-2 has no free LUNs 417 makeParams := func(volume, machine string, size uint64) storage.VolumeAttachmentParams { 418 return storage.VolumeAttachmentParams{ 419 AttachmentParams: storage.AttachmentParams{ 420 Provider: "azure", 421 Machine: names.NewMachineTag(machine), 422 InstanceId: instance.Id("machine-" + machine), 423 }, 424 Volume: names.NewVolumeTag(volume), 425 VolumeId: "volume-" + volume, 426 } 427 } 428 params := []storage.VolumeAttachmentParams{ 429 makeParams("0", "0", 1), 430 makeParams("1", "1", 1025), 431 makeParams("2", "0", 1024), 432 makeParams("3", "42", 40), 433 makeParams("42", "2", 50), 434 } 435 436 virtualMachines := []compute.VirtualMachine{{ 437 Name: to.StringPtr("machine-0"), 438 Properties: &compute.VirtualMachineProperties{ 439 StorageProfile: &compute.StorageProfile{}, 440 }, 441 }, { 442 Name: to.StringPtr("machine-1"), 443 Properties: &compute.VirtualMachineProperties{ 444 StorageProfile: &compute.StorageProfile{DataDisks: &machine1DataDisks}, 445 }, 446 }, { 447 Name: to.StringPtr("machine-2"), 448 Properties: &compute.VirtualMachineProperties{ 449 StorageProfile: &compute.StorageProfile{DataDisks: &machine2DataDisks}, 450 }, 451 }} 452 453 // There should be a one API calls to list VMs, and one update per modified instance. 454 virtualMachinesSender := azuretesting.NewSenderWithValue(compute.VirtualMachineListResult{ 455 Value: &virtualMachines, 456 }) 457 virtualMachinesSender.PathPattern = `.*/Microsoft\.Compute/virtualMachines` 458 updateVirtualMachine0Sender := azuretesting.NewSenderWithValue(&compute.VirtualMachine{}) 459 updateVirtualMachine0Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-0` 460 volumeSource := s.volumeSource(c) 461 s.sender = azuretesting.Senders{ 462 virtualMachinesSender, 463 s.accountSender(), 464 updateVirtualMachine0Sender, 465 } 466 467 results, err := volumeSource.AttachVolumes(params) 468 c.Assert(err, jc.ErrorIsNil) 469 c.Assert(results, gc.HasLen, len(params)) 470 471 c.Check(results[0].Error, jc.ErrorIsNil) 472 c.Check(results[1].Error, jc.ErrorIsNil) 473 c.Check(results[2].Error, jc.ErrorIsNil) 474 c.Check(results[3].Error, gc.ErrorMatches, "instance machine-42 not found") 475 c.Check(results[4].Error, gc.ErrorMatches, "choosing LUN: all LUNs are in use") 476 477 // Validate HTTP request bodies. 478 c.Assert(s.requests, gc.HasLen, 3) 479 c.Assert(s.requests[0].Method, gc.Equals, "GET") // list virtual machines 480 c.Assert(s.requests[1].Method, gc.Equals, "GET") // list storage accounts 481 c.Assert(s.requests[2].Method, gc.Equals, "PUT") // update machine-0 482 483 machine0DataDisks := []compute.DataDisk{{ 484 Lun: to.Int32Ptr(0), 485 Name: to.StringPtr("volume-0"), 486 Vhd: &compute.VirtualHardDisk{URI: to.StringPtr(fmt.Sprintf( 487 "https://%s.blob.storage.azurestack.local/datavhds/volume-0.vhd", 488 storageAccountName, 489 ))}, 490 Caching: compute.ReadWrite, 491 CreateOption: compute.Attach, 492 }, { 493 Lun: to.Int32Ptr(1), 494 Name: to.StringPtr("volume-2"), 495 Vhd: &compute.VirtualHardDisk{URI: to.StringPtr(fmt.Sprintf( 496 "https://%s.blob.storage.azurestack.local/datavhds/volume-2.vhd", 497 storageAccountName, 498 ))}, 499 Caching: compute.ReadWrite, 500 CreateOption: compute.Attach, 501 }} 502 virtualMachines[0].Properties.StorageProfile.DataDisks = &machine0DataDisks 503 assertRequestBody(c, s.requests[2], &virtualMachines[0]) 504 } 505 506 func (s *storageSuite) TestDetachVolumes(c *gc.C) { 507 // machine-0 has a three data disks: volume-0, volume-1 and volume-2 508 machine0DataDisks := []compute.DataDisk{{ 509 Lun: to.Int32Ptr(0), 510 Name: to.StringPtr("volume-0"), 511 Vhd: &compute.VirtualHardDisk{ 512 URI: to.StringPtr(fmt.Sprintf( 513 "https://%s.blob.storage.azurestack.local/datavhds/volume-0.vhd", 514 storageAccountName, 515 )), 516 }, 517 }, { 518 Lun: to.Int32Ptr(1), 519 Name: to.StringPtr("volume-1"), 520 Vhd: &compute.VirtualHardDisk{ 521 URI: to.StringPtr(fmt.Sprintf( 522 "https://%s.blob.storage.azurestack.local/datavhds/volume-1.vhd", 523 storageAccountName, 524 )), 525 }, 526 }, { 527 Lun: to.Int32Ptr(2), 528 Name: to.StringPtr("volume-2"), 529 Vhd: &compute.VirtualHardDisk{ 530 URI: to.StringPtr(fmt.Sprintf( 531 "https://%s.blob.storage.azurestack.local/datavhds/volume-2.vhd", 532 storageAccountName, 533 )), 534 }, 535 }} 536 537 makeParams := func(volume, machine string) storage.VolumeAttachmentParams { 538 return storage.VolumeAttachmentParams{ 539 AttachmentParams: storage.AttachmentParams{ 540 Provider: "azure", 541 Machine: names.NewMachineTag(machine), 542 InstanceId: instance.Id("machine-" + machine), 543 }, 544 Volume: names.NewVolumeTag(volume), 545 VolumeId: "volume-" + volume, 546 } 547 } 548 params := []storage.VolumeAttachmentParams{ 549 makeParams("1", "0"), 550 makeParams("1", "0"), 551 makeParams("42", "1"), 552 makeParams("2", "42"), 553 } 554 555 virtualMachines := []compute.VirtualMachine{{ 556 Name: to.StringPtr("machine-0"), 557 Properties: &compute.VirtualMachineProperties{ 558 StorageProfile: &compute.StorageProfile{DataDisks: &machine0DataDisks}, 559 }, 560 }, { 561 Name: to.StringPtr("machine-1"), 562 Properties: &compute.VirtualMachineProperties{ 563 StorageProfile: &compute.StorageProfile{}, 564 }, 565 }} 566 567 // There should be a one API calls to list VMs, and one update per modified instance. 568 virtualMachinesSender := azuretesting.NewSenderWithValue(compute.VirtualMachineListResult{ 569 Value: &virtualMachines, 570 }) 571 virtualMachinesSender.PathPattern = `.*/Microsoft\.Compute/virtualMachines` 572 updateVirtualMachine0Sender := azuretesting.NewSenderWithValue(&compute.VirtualMachine{}) 573 updateVirtualMachine0Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-0` 574 volumeSource := s.volumeSource(c) 575 s.sender = azuretesting.Senders{ 576 virtualMachinesSender, 577 s.accountSender(), 578 updateVirtualMachine0Sender, 579 } 580 581 results, err := volumeSource.DetachVolumes(params) 582 c.Assert(err, jc.ErrorIsNil) 583 c.Assert(results, gc.HasLen, len(params)) 584 585 c.Check(results[0], jc.ErrorIsNil) 586 c.Check(results[1], jc.ErrorIsNil) 587 c.Check(results[2], jc.ErrorIsNil) 588 c.Check(results[3], gc.ErrorMatches, "instance machine-42 not found") 589 590 // Validate HTTP request bodies. 591 c.Assert(s.requests, gc.HasLen, 3) 592 c.Assert(s.requests[0].Method, gc.Equals, "GET") // list virtual machines 593 c.Assert(s.requests[1].Method, gc.Equals, "GET") // list storage accounts 594 c.Assert(s.requests[2].Method, gc.Equals, "PUT") // update machine-0 595 596 machine0DataDisks = []compute.DataDisk{ 597 machine0DataDisks[0], 598 machine0DataDisks[2], 599 } 600 virtualMachines[0].Properties.StorageProfile.DataDisks = &machine0DataDisks 601 assertRequestBody(c, s.requests[2], &virtualMachines[0]) 602 }