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