github.com/coreos/mantle@v0.13.0/platform/api/azure/storage_mit.go (about)

     1  // Azure VHD Utilities for Go
     2  // Copyright (c) Microsoft Corporation
     3  //
     4  // All rights reserved.
     5  //
     6  // MIT License
     7  //
     8  // Permission is hereby granted, free of charge, to any person obtaining a copy of
     9  // this software and associated documentation files (the "Software"), to deal in
    10  // the Software without restriction, including without limitation the rights to
    11  // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
    12  // of the Software, and to permit persons to whom the Software is furnished to do
    13  // so, subject to the following conditions:
    14  //
    15  // The above copyright notice and this permission notice shall be included in all
    16  // copies or substantial portions of the Software.
    17  //
    18  // THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    19  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    20  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    21  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    22  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    23  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    24  // SOFTWARE.
    25  //
    26  // derived from https://github.com/Microsoft/azure-vhd-utils/blob/8fcb4e03cb4c0f928aa835c21708182dbb23fc83/vhdUploadCmdHandler.go
    27  
    28  package azure
    29  
    30  import (
    31  	"fmt"
    32  	"io"
    33  
    34  	"github.com/Azure/azure-sdk-for-go/storage"
    35  	"github.com/Microsoft/azure-vhd-utils/upload"
    36  	"github.com/Microsoft/azure-vhd-utils/upload/metadata"
    37  	"github.com/Microsoft/azure-vhd-utils/vhdcore/common"
    38  	"github.com/Microsoft/azure-vhd-utils/vhdcore/diskstream"
    39  	"github.com/coreos/pkg/multierror"
    40  )
    41  
    42  const pageBlobPageSize int64 = 2 * 1024 * 1024
    43  
    44  type BlobExistsError string
    45  
    46  func (a *API) ListStorageContainers(storageaccount, storagekey, prefix string) (storage.ContainerListResponse, error) {
    47  	sc, err := storage.NewClient(storageaccount, storagekey, a.opts.StorageEndpointSuffix, storage.DefaultAPIVersion, true)
    48  	if err != nil {
    49  		return storage.ContainerListResponse{}, err
    50  	}
    51  
    52  	bsc := sc.GetBlobService()
    53  
    54  	return bsc.ListContainers(storage.ListContainersParameters{
    55  		Prefix: prefix,
    56  	})
    57  }
    58  
    59  func (a *API) TerminateStorageContainer(storageaccount, storagekey, name string) error {
    60  	sc, err := storage.NewClient(storageaccount, storagekey, a.opts.StorageEndpointSuffix, storage.DefaultAPIVersion, true)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	bsc := sc.GetBlobService()
    66  
    67  	return bsc.DeleteContainer(name)
    68  }
    69  
    70  func (be BlobExistsError) Error() string {
    71  	return fmt.Sprintf("blob %q already exists", string(be))
    72  }
    73  
    74  func (a *API) BlobExists(storageaccount, storagekey, container, blob string) (bool, error) {
    75  	sc, err := storage.NewClient(storageaccount, storagekey, a.opts.StorageEndpointSuffix, storage.DefaultAPIVersion, true)
    76  	if err != nil {
    77  		return false, err
    78  	}
    79  
    80  	bsc := sc.GetBlobService()
    81  
    82  	return bsc.BlobExists(container, blob)
    83  }
    84  
    85  func (a *API) GetBlob(storageaccount, storagekey, container, name string) (io.ReadCloser, error) {
    86  	sc, err := storage.NewClient(storageaccount, storagekey, a.opts.StorageEndpointSuffix, storage.DefaultAPIVersion, true)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	bsc := sc.GetBlobService()
    92  	if _, err = bsc.CreateContainerIfNotExists(container, storage.ContainerAccessTypePrivate); err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	return bsc.GetBlob(container, name)
    97  }
    98  
    99  // UploadBlob uploads vhd to the given storage account, container, and blob name.
   100  //
   101  // It returns BlobExistsError if the blob exists and overwrite is not true.
   102  func (a *API) UploadBlob(storageaccount, storagekey, vhd, container, blob string, overwrite bool) error {
   103  	ds, err := diskstream.CreateNewDiskStream(vhd)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer ds.Close()
   108  
   109  	sc, err := storage.NewClient(storageaccount, storagekey, a.opts.StorageEndpointSuffix, storage.DefaultAPIVersion, true)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	bsc := sc.GetBlobService()
   115  	if _, err = bsc.CreateContainerIfNotExists(container, storage.ContainerAccessTypePrivate); err != nil {
   116  		return err
   117  	}
   118  
   119  	blobExists, err := bsc.BlobExists(container, blob)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	resume := false
   125  	var blobMetaData *metadata.MetaData
   126  	if blobExists {
   127  		if !overwrite {
   128  			bm, err := getBlobMetaData(bsc, container, blob)
   129  			if err != nil {
   130  				return err
   131  			}
   132  			blobMetaData = bm
   133  			resume = true
   134  			plog.Printf("Blob with name '%s' already exists, checking if upload can be resumed", blob)
   135  		}
   136  	}
   137  
   138  	localMetaData, err := getLocalVHDMetaData(vhd)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	var rangesToSkip []*common.IndexRange
   143  	if resume {
   144  		if errs := metadata.CompareMetaData(blobMetaData, localMetaData); len(errs) != 0 {
   145  			return multierror.Error(errs)
   146  		}
   147  		ranges, err := getAlreadyUploadedBlobRanges(bsc, container, blob)
   148  		if err != nil {
   149  			return err
   150  		}
   151  		rangesToSkip = ranges
   152  	} else {
   153  		if err := createBlob(bsc, container, blob, ds.GetSize(), localMetaData); err != nil {
   154  			return err
   155  		}
   156  	}
   157  
   158  	uploadableRanges, err := upload.LocateUploadableRanges(ds, rangesToSkip, pageBlobPageSize)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	uploadableRanges, err = upload.DetectEmptyRanges(ds, uploadableRanges)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	cxt := &upload.DiskUploadContext{
   169  		VhdStream:             ds,
   170  		UploadableRanges:      uploadableRanges,
   171  		AlreadyProcessedBytes: common.TotalRangeLength(rangesToSkip),
   172  		BlobServiceClient:     bsc,
   173  		ContainerName:         container,
   174  		BlobName:              blob,
   175  		Parallelism:           8,
   176  		Resume:                resume,
   177  		MD5Hash:               localMetaData.FileMetaData.MD5Hash,
   178  	}
   179  
   180  	return upload.Upload(cxt)
   181  }
   182  
   183  // getBlobMetaData returns the custom metadata associated with a page blob which is set by createBlob method.
   184  // The parameter client is the Azure blob service client, parameter containerName is the name of an existing container
   185  // in which the page blob resides, parameter blobName is name for the page blob
   186  // This method attempt to fetch the metadata only if MD5Hash is not set for the page blob, this method panic if the
   187  // MD5Hash is already set or if the custom metadata is absent.
   188  //
   189  func getBlobMetaData(client storage.BlobStorageClient, containerName, blobName string) (*metadata.MetaData, error) {
   190  	md5Hash, err := getBlobMD5Hash(client, containerName, blobName)
   191  	if md5Hash != "" {
   192  		return nil, BlobExistsError(blobName)
   193  	}
   194  
   195  	blobMetaData, err := metadata.NewMetadataFromBlob(client, containerName, blobName)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	if blobMetaData == nil {
   201  		return nil, fmt.Errorf("There is no upload metadata associated with the existing blob '%s', so upload operation cannot be resumed, use --overwrite option.", blobName)
   202  	}
   203  
   204  	return blobMetaData, nil
   205  }
   206  
   207  // getLocalVHDMetaData returns the metadata of a local VHD
   208  //
   209  func getLocalVHDMetaData(localVHDPath string) (*metadata.MetaData, error) {
   210  	localMetaData, err := metadata.NewMetaDataFromLocalVHD(localVHDPath)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	return localMetaData, nil
   215  }
   216  
   217  // createBlob creates a page blob of specific size and sets custom metadata
   218  // The parameter client is the Azure blob service client, parameter containerName is the name of an existing container
   219  // in which the page blob needs to be created, parameter blobName is name for the new page blob, size is the size of
   220  // the new page blob in bytes and parameter vhdMetaData is the custom metadata to be associacted with the page blob
   221  //
   222  func createBlob(client storage.BlobStorageClient, containerName, blobName string, size int64, vhdMetaData *metadata.MetaData) error {
   223  	if err := client.PutPageBlob(containerName, blobName, size, nil); err != nil {
   224  		return err
   225  	}
   226  	m, _ := vhdMetaData.ToMap()
   227  	if err := client.SetBlobMetadata(containerName, blobName, m, make(map[string]string)); err != nil {
   228  		return err
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  // getAlreadyUploadedBlobRanges returns the range slice containing ranges of a page blob those are already uploaded.
   235  // The parameter client is the Azure blob service client, parameter containerName is the name of an existing container
   236  // in which the page blob resides, parameter blobName is name for the page blob
   237  //
   238  func getAlreadyUploadedBlobRanges(client storage.BlobStorageClient, containerName, blobName string) ([]*common.IndexRange, error) {
   239  	existingRanges, err := client.GetPageRanges(containerName, blobName)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	var rangesToSkip = make([]*common.IndexRange, len(existingRanges.PageList))
   244  	for i, r := range existingRanges.PageList {
   245  		rangesToSkip[i] = common.NewIndexRange(r.Start, r.End)
   246  	}
   247  	return rangesToSkip, nil
   248  }
   249  
   250  // getBlobMD5Hash returns the MD5Hash associated with a blob
   251  // The parameter client is the Azure blob service client, parameter containerName is the name of an existing container
   252  // in which the page blob resides, parameter blobName is name for the page blob
   253  //
   254  func getBlobMD5Hash(client storage.BlobStorageClient, containerName, blobName string) (string, error) {
   255  	properties, err := client.GetBlobProperties(containerName, blobName)
   256  	if err != nil {
   257  		return "", err
   258  	}
   259  	return properties.ContentMD5, nil
   260  }