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 }