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  }