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  }