github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/azure/storage.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azure
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/Azure/azure-sdk-for-go/arm/compute"
    12  	armstorage "github.com/Azure/azure-sdk-for-go/arm/storage"
    13  	azurestorage "github.com/Azure/azure-sdk-for-go/storage"
    14  	"github.com/Azure/go-autorest/autorest"
    15  	"github.com/Azure/go-autorest/autorest/to"
    16  	"github.com/juju/errors"
    17  	"github.com/juju/schema"
    18  	"gopkg.in/juju/names.v2"
    19  
    20  	"github.com/juju/juju/instance"
    21  	"github.com/juju/juju/provider/azure/internal/armtemplates"
    22  	internalazurestorage "github.com/juju/juju/provider/azure/internal/azurestorage"
    23  	"github.com/juju/juju/storage"
    24  )
    25  
    26  const (
    27  	azureStorageProviderType = "azure"
    28  
    29  	// volumeSizeMaxGiB is the maximum disk size (in gibibytes) for Azure disks.
    30  	//
    31  	// See: https://azure.microsoft.com/en-gb/documentation/articles/virtual-machines-disks-vhds/
    32  	volumeSizeMaxGiB = 1023
    33  
    34  	// osDiskVHDContainer is the name of the blob container for VHDs
    35  	// backing OS disks.
    36  	osDiskVHDContainer = "osvhds"
    37  
    38  	// dataDiskVHDContainer is the name of the blob container for VHDs
    39  	// backing data disks.
    40  	dataDiskVHDContainer = "datavhds"
    41  
    42  	// vhdExtension is the filename extension we give to VHDs we create.
    43  	vhdExtension = ".vhd"
    44  )
    45  
    46  // StorageProviderTypes implements storage.ProviderRegistry.
    47  func (env *azureEnviron) StorageProviderTypes() ([]storage.ProviderType, error) {
    48  	return []storage.ProviderType{azureStorageProviderType}, nil
    49  }
    50  
    51  // StorageProvider implements storage.ProviderRegistry.
    52  func (env *azureEnviron) StorageProvider(t storage.ProviderType) (storage.Provider, error) {
    53  	if t == azureStorageProviderType {
    54  		return &azureStorageProvider{env}, nil
    55  	}
    56  	return nil, errors.NotFoundf("storage provider %q", t)
    57  }
    58  
    59  // azureStorageProvider is a storage provider for Azure disks.
    60  type azureStorageProvider struct {
    61  	env *azureEnviron
    62  }
    63  
    64  var _ storage.Provider = (*azureStorageProvider)(nil)
    65  
    66  var azureStorageConfigFields = schema.Fields{}
    67  
    68  var azureStorageConfigChecker = schema.FieldMap(
    69  	azureStorageConfigFields,
    70  	schema.Defaults{},
    71  )
    72  
    73  type azureStorageConfig struct {
    74  }
    75  
    76  func newAzureStorageConfig(attrs map[string]interface{}) (*azureStorageConfig, error) {
    77  	_, err := azureStorageConfigChecker.Coerce(attrs, nil)
    78  	if err != nil {
    79  		return nil, errors.Annotate(err, "validating Azure storage config")
    80  	}
    81  	azureStorageConfig := &azureStorageConfig{}
    82  	return azureStorageConfig, nil
    83  }
    84  
    85  // ValidateConfig is part of the Provider interface.
    86  func (e *azureStorageProvider) ValidateConfig(cfg *storage.Config) error {
    87  	_, err := newAzureStorageConfig(cfg.Attrs())
    88  	return errors.Trace(err)
    89  }
    90  
    91  // Supports is part of the Provider interface.
    92  func (e *azureStorageProvider) Supports(k storage.StorageKind) bool {
    93  	return k == storage.StorageKindBlock
    94  }
    95  
    96  // Scope is part of the Provider interface.
    97  func (e *azureStorageProvider) Scope() storage.Scope {
    98  	return storage.ScopeEnviron
    99  }
   100  
   101  // Dynamic is part of the Provider interface.
   102  func (e *azureStorageProvider) Dynamic() bool {
   103  	return true
   104  }
   105  
   106  // DefaultPools is part of the Provider interface.
   107  func (e *azureStorageProvider) DefaultPools() []*storage.Config {
   108  	return nil
   109  }
   110  
   111  // VolumeSource is part of the Provider interface.
   112  func (e *azureStorageProvider) VolumeSource(cfg *storage.Config) (storage.VolumeSource, error) {
   113  	if err := e.ValidateConfig(cfg); err != nil {
   114  		return nil, errors.Trace(err)
   115  	}
   116  	return &azureVolumeSource{e.env}, nil
   117  }
   118  
   119  // FilesystemSource is part of the Provider interface.
   120  func (e *azureStorageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) {
   121  	return nil, errors.NotSupportedf("filesystems")
   122  }
   123  
   124  type azureVolumeSource struct {
   125  	env *azureEnviron
   126  }
   127  
   128  // CreateVolumes is specified on the storage.VolumeSource interface.
   129  func (v *azureVolumeSource) CreateVolumes(params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) {
   130  
   131  	// First, validate the params before we use them.
   132  	results := make([]storage.CreateVolumesResult, len(params))
   133  	var instanceIds []instance.Id
   134  	for i, p := range params {
   135  		if err := v.ValidateVolumeParams(p); err != nil {
   136  			results[i].Error = err
   137  			continue
   138  		}
   139  		instanceIds = append(instanceIds, p.Attachment.InstanceId)
   140  	}
   141  	if len(instanceIds) == 0 {
   142  		return results, nil
   143  	}
   144  	virtualMachines, err := v.virtualMachines(instanceIds)
   145  	if err != nil {
   146  		return nil, errors.Annotate(err, "getting virtual machines")
   147  	}
   148  	storageAccount, err := v.env.getStorageAccount(false)
   149  	if err != nil {
   150  		return nil, errors.Trace(err)
   151  	}
   152  
   153  	// Update VirtualMachine objects in-memory,
   154  	// and then perform the updates all at once.
   155  	for i, p := range params {
   156  		if results[i].Error != nil {
   157  			continue
   158  		}
   159  		vm, ok := virtualMachines[p.Attachment.InstanceId]
   160  		if !ok {
   161  			continue
   162  		}
   163  		if vm.err != nil {
   164  			results[i].Error = vm.err
   165  			continue
   166  		}
   167  		volume, volumeAttachment, err := v.createVolume(
   168  			vm.vm, p, storageAccount,
   169  		)
   170  		if err != nil {
   171  			results[i].Error = err
   172  			vm.err = err
   173  			continue
   174  		}
   175  		results[i].Volume = volume
   176  		results[i].VolumeAttachment = volumeAttachment
   177  	}
   178  
   179  	updateResults, err := v.updateVirtualMachines(virtualMachines, instanceIds)
   180  	if err != nil {
   181  		return nil, errors.Annotate(err, "updating virtual machines")
   182  	}
   183  	for i, err := range updateResults {
   184  		if results[i].Error != nil || err == nil {
   185  			continue
   186  		}
   187  		results[i].Error = err
   188  		results[i].Volume = nil
   189  		results[i].VolumeAttachment = nil
   190  	}
   191  	return results, nil
   192  }
   193  
   194  // createVolume updates the provided VirtualMachine's StorageProfile with the
   195  // parameters for creating a new data disk. We don't actually interact with
   196  // the Azure API until after all changes to the VirtualMachine are made.
   197  func (v *azureVolumeSource) createVolume(
   198  	vm *compute.VirtualMachine,
   199  	p storage.VolumeParams,
   200  	storageAccount *armstorage.Account,
   201  ) (*storage.Volume, *storage.VolumeAttachment, error) {
   202  
   203  	lun, err := nextAvailableLUN(vm)
   204  	if err != nil {
   205  		return nil, nil, errors.Annotate(err, "choosing LUN")
   206  	}
   207  
   208  	dataDisksRoot := dataDiskVhdRoot(storageAccount)
   209  	dataDiskName := p.Tag.String()
   210  	vhdURI := dataDisksRoot + dataDiskName + vhdExtension
   211  
   212  	sizeInGib := mibToGib(p.Size)
   213  	dataDisk := compute.DataDisk{
   214  		Lun:          to.Int32Ptr(lun),
   215  		DiskSizeGB:   to.Int32Ptr(int32(sizeInGib)),
   216  		Name:         to.StringPtr(dataDiskName),
   217  		Vhd:          &compute.VirtualHardDisk{to.StringPtr(vhdURI)},
   218  		Caching:      compute.ReadWrite,
   219  		CreateOption: compute.Empty,
   220  	}
   221  
   222  	var dataDisks []compute.DataDisk
   223  	if vm.Properties.StorageProfile.DataDisks != nil {
   224  		dataDisks = *vm.Properties.StorageProfile.DataDisks
   225  	}
   226  	dataDisks = append(dataDisks, dataDisk)
   227  	vm.Properties.StorageProfile.DataDisks = &dataDisks
   228  
   229  	// Data disks associate VHDs to machines. In Juju's storage model,
   230  	// the VHD is the volume and the disk is the volume attachment.
   231  	volume := storage.Volume{
   232  		p.Tag,
   233  		storage.VolumeInfo{
   234  			VolumeId: dataDiskName,
   235  			Size:     gibToMib(sizeInGib),
   236  			// We don't currently support persistent volumes in
   237  			// Azure, as it requires removal of "comp=media" when
   238  			// deleting VMs, complicating cleanup.
   239  			Persistent: true,
   240  		},
   241  	}
   242  	volumeAttachment := storage.VolumeAttachment{
   243  		p.Tag,
   244  		p.Attachment.Machine,
   245  		storage.VolumeAttachmentInfo{
   246  			BusAddress: diskBusAddress(lun),
   247  		},
   248  	}
   249  	return &volume, &volumeAttachment, nil
   250  }
   251  
   252  // ListVolumes is specified on the storage.VolumeSource interface.
   253  func (v *azureVolumeSource) ListVolumes() ([]string, error) {
   254  	blobs, err := v.listBlobs()
   255  	if err != nil {
   256  		return nil, errors.Annotate(err, "listing volumes")
   257  	}
   258  	volumeIds := make([]string, 0, len(blobs))
   259  	for _, blob := range blobs {
   260  		volumeId, ok := blobVolumeId(blob)
   261  		if !ok {
   262  			continue
   263  		}
   264  		volumeIds = append(volumeIds, volumeId)
   265  	}
   266  	return volumeIds, nil
   267  }
   268  
   269  // listBlobs returns a list of blobs in the data-disk container.
   270  func (v *azureVolumeSource) listBlobs() ([]azurestorage.Blob, error) {
   271  	client, err := v.env.getStorageClient()
   272  	if err != nil {
   273  		return nil, errors.Trace(err)
   274  	}
   275  	blobsClient := client.GetBlobService()
   276  	// TODO(axw) handle pagination
   277  	// TODO(axw) consider taking a set of IDs and computing the
   278  	//           longest common prefix to pass in the parameters
   279  	response, err := blobsClient.ListBlobs(
   280  		dataDiskVHDContainer, azurestorage.ListBlobsParameters{},
   281  	)
   282  	if err != nil {
   283  		if err, ok := err.(azurestorage.AzureStorageServiceError); ok {
   284  			switch err.Code {
   285  			case "ContainerNotFound":
   286  				return nil, nil
   287  			}
   288  		}
   289  		return nil, errors.Annotate(err, "listing blobs")
   290  	}
   291  	return response.Blobs, nil
   292  }
   293  
   294  // DescribeVolumes is specified on the storage.VolumeSource interface.
   295  func (v *azureVolumeSource) DescribeVolumes(volumeIds []string) ([]storage.DescribeVolumesResult, error) {
   296  	blobs, err := v.listBlobs()
   297  	if err != nil {
   298  		return nil, errors.Annotate(err, "listing volumes")
   299  	}
   300  
   301  	byVolumeId := make(map[string]azurestorage.Blob)
   302  	for _, blob := range blobs {
   303  		volumeId, ok := blobVolumeId(blob)
   304  		if !ok {
   305  			continue
   306  		}
   307  		byVolumeId[volumeId] = blob
   308  	}
   309  
   310  	results := make([]storage.DescribeVolumesResult, len(volumeIds))
   311  	for i, volumeId := range volumeIds {
   312  		blob, ok := byVolumeId[volumeId]
   313  		if !ok {
   314  			results[i].Error = errors.NotFoundf("%s", volumeId)
   315  			continue
   316  		}
   317  		sizeInMib := blob.Properties.ContentLength / (1024 * 1024)
   318  		results[i].VolumeInfo = &storage.VolumeInfo{
   319  			VolumeId:   volumeId,
   320  			Size:       uint64(sizeInMib),
   321  			Persistent: true,
   322  		}
   323  	}
   324  
   325  	return results, nil
   326  }
   327  
   328  // DestroyVolumes is specified on the storage.VolumeSource interface.
   329  func (v *azureVolumeSource) DestroyVolumes(volumeIds []string) ([]error, error) {
   330  	client, err := v.env.getStorageClient()
   331  	if err != nil {
   332  		return nil, errors.Trace(err)
   333  	}
   334  	blobsClient := client.GetBlobService()
   335  	results := make([]error, len(volumeIds))
   336  	for i, volumeId := range volumeIds {
   337  		_, err := blobsClient.DeleteBlobIfExists(
   338  			dataDiskVHDContainer, volumeId+vhdExtension, nil,
   339  		)
   340  		results[i] = err
   341  	}
   342  	return results, nil
   343  }
   344  
   345  // ValidateVolumeParams is specified on the storage.VolumeSource interface.
   346  func (v *azureVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
   347  	if mibToGib(params.Size) > volumeSizeMaxGiB {
   348  		return errors.Errorf(
   349  			"%d GiB exceeds the maximum of %d GiB",
   350  			mibToGib(params.Size),
   351  			volumeSizeMaxGiB,
   352  		)
   353  	}
   354  	return nil
   355  }
   356  
   357  // AttachVolumes is specified on the storage.VolumeSource interface.
   358  func (v *azureVolumeSource) AttachVolumes(attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   359  	results := make([]storage.AttachVolumesResult, len(attachParams))
   360  	instanceIds := make([]instance.Id, len(attachParams))
   361  	for i, p := range attachParams {
   362  		instanceIds[i] = p.InstanceId
   363  	}
   364  	if len(instanceIds) == 0 {
   365  		return results, nil
   366  	}
   367  	virtualMachines, err := v.virtualMachines(instanceIds)
   368  	if err != nil {
   369  		return nil, errors.Annotate(err, "getting virtual machines")
   370  	}
   371  	storageAccount, err := v.env.getStorageAccount(false)
   372  	if err != nil {
   373  		return nil, errors.Trace(err)
   374  	}
   375  
   376  	// Update VirtualMachine objects in-memory,
   377  	// and then perform the updates all at once.
   378  	//
   379  	// An attachment does not require an update
   380  	// if it is pre-existing, so we keep a record
   381  	// of which VMs need updating.
   382  	changed := make(map[instance.Id]bool, len(virtualMachines))
   383  	for i, p := range attachParams {
   384  		vm, ok := virtualMachines[p.InstanceId]
   385  		if !ok {
   386  			continue
   387  		}
   388  		if vm.err != nil {
   389  			results[i].Error = vm.err
   390  			continue
   391  		}
   392  		volumeAttachment, updated, err := v.attachVolume(
   393  			vm.vm, p, storageAccount,
   394  		)
   395  		if err != nil {
   396  			results[i].Error = err
   397  			vm.err = err
   398  			continue
   399  		}
   400  		results[i].VolumeAttachment = volumeAttachment
   401  		if updated {
   402  			changed[p.InstanceId] = true
   403  		}
   404  	}
   405  	for _, instanceId := range instanceIds {
   406  		if !changed[instanceId] {
   407  			delete(virtualMachines, instanceId)
   408  		}
   409  	}
   410  
   411  	updateResults, err := v.updateVirtualMachines(virtualMachines, instanceIds)
   412  	if err != nil {
   413  		return nil, errors.Annotate(err, "updating virtual machines")
   414  	}
   415  	for i, err := range updateResults {
   416  		if results[i].Error != nil || err == nil {
   417  			continue
   418  		}
   419  		results[i].Error = err
   420  		results[i].VolumeAttachment = nil
   421  	}
   422  	return results, nil
   423  }
   424  
   425  func (v *azureVolumeSource) attachVolume(
   426  	vm *compute.VirtualMachine,
   427  	p storage.VolumeAttachmentParams,
   428  	storageAccount *armstorage.Account,
   429  ) (_ *storage.VolumeAttachment, updated bool, _ error) {
   430  
   431  	storageAccount, err := v.env.getStorageAccount(false)
   432  	if err != nil {
   433  		return nil, false, errors.Trace(err)
   434  	}
   435  
   436  	dataDisksRoot := dataDiskVhdRoot(storageAccount)
   437  	dataDiskName := p.VolumeId
   438  	vhdURI := dataDisksRoot + dataDiskName + vhdExtension
   439  
   440  	var dataDisks []compute.DataDisk
   441  	if vm.Properties.StorageProfile.DataDisks != nil {
   442  		dataDisks = *vm.Properties.StorageProfile.DataDisks
   443  	}
   444  	for _, disk := range dataDisks {
   445  		if to.String(disk.Name) != p.VolumeId {
   446  			continue
   447  		}
   448  		if to.String(disk.Vhd.URI) != vhdURI {
   449  			continue
   450  		}
   451  		// Disk is already attached.
   452  		volumeAttachment := &storage.VolumeAttachment{
   453  			p.Volume,
   454  			p.Machine,
   455  			storage.VolumeAttachmentInfo{
   456  				BusAddress: diskBusAddress(to.Int32(disk.Lun)),
   457  			},
   458  		}
   459  		return volumeAttachment, false, nil
   460  	}
   461  
   462  	lun, err := nextAvailableLUN(vm)
   463  	if err != nil {
   464  		return nil, false, errors.Annotate(err, "choosing LUN")
   465  	}
   466  
   467  	dataDisk := compute.DataDisk{
   468  		Lun:          to.Int32Ptr(lun),
   469  		Name:         to.StringPtr(dataDiskName),
   470  		Vhd:          &compute.VirtualHardDisk{to.StringPtr(vhdURI)},
   471  		Caching:      compute.ReadWrite,
   472  		CreateOption: compute.Attach,
   473  	}
   474  	dataDisks = append(dataDisks, dataDisk)
   475  	vm.Properties.StorageProfile.DataDisks = &dataDisks
   476  
   477  	volumeAttachment := storage.VolumeAttachment{
   478  		p.Volume,
   479  		p.Machine,
   480  		storage.VolumeAttachmentInfo{
   481  			BusAddress: diskBusAddress(lun),
   482  		},
   483  	}
   484  	return &volumeAttachment, true, nil
   485  }
   486  
   487  // DetachVolumes is specified on the storage.VolumeSource interface.
   488  func (v *azureVolumeSource) DetachVolumes(attachParams []storage.VolumeAttachmentParams) ([]error, error) {
   489  	results := make([]error, len(attachParams))
   490  	instanceIds := make([]instance.Id, len(attachParams))
   491  	for i, p := range attachParams {
   492  		instanceIds[i] = p.InstanceId
   493  	}
   494  	if len(instanceIds) == 0 {
   495  		return results, nil
   496  	}
   497  	virtualMachines, err := v.virtualMachines(instanceIds)
   498  	if err != nil {
   499  		return nil, errors.Annotate(err, "getting virtual machines")
   500  	}
   501  	storageAccount, err := v.env.getStorageAccount(false)
   502  	if err != nil {
   503  		return nil, errors.Annotate(err, "getting storage account")
   504  	}
   505  
   506  	// Update VirtualMachine objects in-memory,
   507  	// and then perform the updates all at once.
   508  	//
   509  	// An detachment does not require an update
   510  	// if the disk isn't attached, so we keep a
   511  	// record of which VMs need updating.
   512  	changed := make(map[instance.Id]bool, len(virtualMachines))
   513  	for i, p := range attachParams {
   514  		vm, ok := virtualMachines[p.InstanceId]
   515  		if !ok {
   516  			continue
   517  		}
   518  		if vm.err != nil {
   519  			results[i] = vm.err
   520  			continue
   521  		}
   522  		if v.detachVolume(vm.vm, p, storageAccount) {
   523  			changed[p.InstanceId] = true
   524  		}
   525  	}
   526  	for _, instanceId := range instanceIds {
   527  		if !changed[instanceId] {
   528  			delete(virtualMachines, instanceId)
   529  		}
   530  	}
   531  
   532  	updateResults, err := v.updateVirtualMachines(virtualMachines, instanceIds)
   533  	if err != nil {
   534  		return nil, errors.Annotate(err, "updating virtual machines")
   535  	}
   536  	for i, err := range updateResults {
   537  		if results[i] != nil || err == nil {
   538  			continue
   539  		}
   540  		results[i] = err
   541  	}
   542  	return results, nil
   543  }
   544  
   545  func (v *azureVolumeSource) detachVolume(
   546  	vm *compute.VirtualMachine,
   547  	p storage.VolumeAttachmentParams,
   548  	storageAccount *armstorage.Account,
   549  ) (updated bool) {
   550  
   551  	dataDisksRoot := dataDiskVhdRoot(storageAccount)
   552  	dataDiskName := p.VolumeId
   553  	vhdURI := dataDisksRoot + dataDiskName + vhdExtension
   554  
   555  	var dataDisks []compute.DataDisk
   556  	if vm.Properties.StorageProfile.DataDisks != nil {
   557  		dataDisks = *vm.Properties.StorageProfile.DataDisks
   558  	}
   559  	for i, disk := range dataDisks {
   560  		if to.String(disk.Name) != p.VolumeId {
   561  			continue
   562  		}
   563  		if to.String(disk.Vhd.URI) != vhdURI {
   564  			continue
   565  		}
   566  		dataDisks = append(dataDisks[:i], dataDisks[i+1:]...)
   567  		if len(dataDisks) == 0 {
   568  			vm.Properties.StorageProfile.DataDisks = nil
   569  		} else {
   570  			*vm.Properties.StorageProfile.DataDisks = dataDisks
   571  		}
   572  		return true
   573  	}
   574  	return false
   575  }
   576  
   577  type maybeVirtualMachine struct {
   578  	vm  *compute.VirtualMachine
   579  	err error
   580  }
   581  
   582  // virtualMachines returns a mapping of instance IDs to VirtualMachines and
   583  // errors, for each of the specified instance IDs.
   584  func (v *azureVolumeSource) virtualMachines(instanceIds []instance.Id) (map[instance.Id]*maybeVirtualMachine, error) {
   585  	vmsClient := compute.VirtualMachinesClient{v.env.compute}
   586  	var result compute.VirtualMachineListResult
   587  	if err := v.env.callAPI(func() (autorest.Response, error) {
   588  		var err error
   589  		result, err = vmsClient.List(v.env.resourceGroup)
   590  		return result.Response, err
   591  	}); err != nil {
   592  		return nil, errors.Annotate(err, "listing virtual machines")
   593  	}
   594  
   595  	all := make(map[instance.Id]*compute.VirtualMachine)
   596  	if result.Value != nil {
   597  		for _, vm := range *result.Value {
   598  			vmCopy := vm
   599  			all[instance.Id(to.String(vm.Name))] = &vmCopy
   600  		}
   601  	}
   602  	results := make(map[instance.Id]*maybeVirtualMachine)
   603  	for _, id := range instanceIds {
   604  		result := &maybeVirtualMachine{vm: all[id]}
   605  		if result.vm == nil {
   606  			result.err = errors.NotFoundf("instance %v", id)
   607  		}
   608  		results[id] = result
   609  	}
   610  	return results, nil
   611  }
   612  
   613  // updateVirtualMachines updates virtual machines in the given map by iterating
   614  // through the list of instance IDs in order, and updating each corresponding
   615  // virtual machine at most once.
   616  func (v *azureVolumeSource) updateVirtualMachines(
   617  	virtualMachines map[instance.Id]*maybeVirtualMachine, instanceIds []instance.Id,
   618  ) ([]error, error) {
   619  	results := make([]error, len(instanceIds))
   620  	vmsClient := compute.VirtualMachinesClient{v.env.compute}
   621  	for i, instanceId := range instanceIds {
   622  		vm, ok := virtualMachines[instanceId]
   623  		if !ok {
   624  			continue
   625  		}
   626  		if vm.err != nil {
   627  			results[i] = vm.err
   628  			continue
   629  		}
   630  		if err := v.env.callAPI(func() (autorest.Response, error) {
   631  			return vmsClient.CreateOrUpdate(
   632  				v.env.resourceGroup, to.String(vm.vm.Name), *vm.vm,
   633  				nil, // abort channel
   634  			)
   635  		}); err != nil {
   636  			results[i] = err
   637  			vm.err = err
   638  			continue
   639  		}
   640  		// successfully updated, don't update again
   641  		delete(virtualMachines, instanceId)
   642  	}
   643  	return results, nil
   644  }
   645  
   646  func nextAvailableLUN(vm *compute.VirtualMachine) (int32, error) {
   647  	// Pick the smallest LUN not in use. We have to choose them in order,
   648  	// or the disks don't show up.
   649  	var inUse [32]bool
   650  	if vm.Properties.StorageProfile.DataDisks != nil {
   651  		for _, disk := range *vm.Properties.StorageProfile.DataDisks {
   652  			lun := to.Int32(disk.Lun)
   653  			if lun < 0 || lun > 31 {
   654  				logger.Debugf("ignore disk with invalid LUN: %+v", disk)
   655  				continue
   656  			}
   657  			inUse[lun] = true
   658  		}
   659  	}
   660  	for i, inUse := range inUse {
   661  		if !inUse {
   662  			return int32(i), nil
   663  		}
   664  	}
   665  	return -1, errors.New("all LUNs are in use")
   666  }
   667  
   668  // diskBusAddress returns the value to use in the BusAddress field of
   669  // VolumeAttachmentInfo for a disk with the specified LUN.
   670  func diskBusAddress(lun int32) string {
   671  	return fmt.Sprintf("scsi@5:0.0.%d", lun)
   672  }
   673  
   674  // mibToGib converts mebibytes to gibibytes.
   675  // AWS expects GiB, we work in MiB; round up
   676  // to nearest GiB.
   677  func mibToGib(m uint64) uint64 {
   678  	return (m + 1023) / 1024
   679  }
   680  
   681  // gibToMib converts gibibytes to mebibytes.
   682  func gibToMib(g uint64) uint64 {
   683  	return g * 1024
   684  }
   685  
   686  // osDiskVhdRoot returns the URL to the blob container in which we store the
   687  // VHDs for OS disks for the environment.
   688  func osDiskVhdRoot(storageAccount *armstorage.Account) string {
   689  	return blobContainerURL(storageAccount, osDiskVHDContainer)
   690  }
   691  
   692  // dataDiskVhdRoot returns the URL to the blob container in which we store the
   693  // VHDs for data disks for the environment.
   694  func dataDiskVhdRoot(storageAccount *armstorage.Account) string {
   695  	return blobContainerURL(storageAccount, dataDiskVHDContainer)
   696  }
   697  
   698  // blobContainer returns the URL to the named blob container.
   699  func blobContainerURL(storageAccount *armstorage.Account, container string) string {
   700  	return fmt.Sprintf(
   701  		"%s%s/",
   702  		to.String(storageAccount.Properties.PrimaryEndpoints.Blob),
   703  		container,
   704  	)
   705  }
   706  
   707  // blobVolumeId returns the volume ID for a blob, and a boolean reporting
   708  // whether or not the blob's name matches the scheme we use.
   709  func blobVolumeId(blob azurestorage.Blob) (string, bool) {
   710  	if !strings.HasSuffix(blob.Name, vhdExtension) {
   711  		return "", false
   712  	}
   713  	volumeId := blob.Name[:len(blob.Name)-len(vhdExtension)]
   714  	if _, err := names.ParseVolumeTag(volumeId); err != nil {
   715  		return "", false
   716  	}
   717  	return volumeId, true
   718  }
   719  
   720  // getStorageClient returns a new storage client, given an environ config
   721  // and a constructor.
   722  func getStorageClient(
   723  	newClient internalazurestorage.NewClientFunc,
   724  	storageEndpoint string,
   725  	storageAccount *armstorage.Account,
   726  	storageAccountKey *armstorage.AccountKey,
   727  ) (internalazurestorage.Client, error) {
   728  	storageAccountName := to.String(storageAccount.Name)
   729  	const useHTTPS = true
   730  	return newClient(
   731  		storageAccountName,
   732  		to.String(storageAccountKey.Value),
   733  		storageEndpoint,
   734  		azurestorage.DefaultAPIVersion,
   735  		useHTTPS,
   736  	)
   737  }
   738  
   739  // getStorageAccountKey returns the key for the storage account.
   740  func getStorageAccountKey(
   741  	callAPI callAPIFunc,
   742  	client armstorage.AccountsClient,
   743  	resourceGroup, accountName string,
   744  ) (*armstorage.AccountKey, error) {
   745  	logger.Debugf("getting keys for storage account %q", accountName)
   746  	var listKeysResult armstorage.AccountListKeysResult
   747  	if err := callAPI(func() (autorest.Response, error) {
   748  		var err error
   749  		listKeysResult, err = client.ListKeys(resourceGroup, accountName)
   750  		return listKeysResult.Response, err
   751  	}); err != nil {
   752  		if listKeysResult.Response.Response != nil && listKeysResult.StatusCode == http.StatusNotFound {
   753  			return nil, errors.NewNotFound(err, "storage account keys not found")
   754  		}
   755  		return nil, errors.Annotate(err, "listing storage account keys")
   756  	}
   757  	if listKeysResult.Keys == nil {
   758  		return nil, errors.NotFoundf("storage account keys")
   759  	}
   760  
   761  	// We need a storage key with full permissions.
   762  	var fullKey *armstorage.AccountKey
   763  	for _, key := range *listKeysResult.Keys {
   764  		logger.Debugf("storage account key: %#v", key)
   765  		// At least some of the time, Azure returns the permissions
   766  		// in title-case, which does not match the constant.
   767  		if strings.ToUpper(string(key.Permissions)) != string(armstorage.FULL) {
   768  			continue
   769  		}
   770  		fullKey = &key
   771  		break
   772  	}
   773  	if fullKey == nil {
   774  		return nil, errors.NotFoundf(
   775  			"storage account key with %q permission",
   776  			armstorage.FULL,
   777  		)
   778  	}
   779  	return fullKey, nil
   780  }
   781  
   782  // storageAccountTemplateResource returns a template resource definition
   783  // for creating a storage account.
   784  func storageAccountTemplateResource(
   785  	location string,
   786  	envTags map[string]string,
   787  	accountName, accountType string,
   788  ) armtemplates.Resource {
   789  	return armtemplates.Resource{
   790  		APIVersion: armstorage.APIVersion,
   791  		Type:       "Microsoft.Storage/storageAccounts",
   792  		Name:       accountName,
   793  		Location:   location,
   794  		Tags:       envTags,
   795  		StorageSku: &armstorage.Sku{
   796  			Name: armstorage.SkuName(accountType),
   797  		},
   798  	}
   799  }