| package storage |
| |
| // Copyright 2017 Microsoft Corporation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import ( |
| "encoding/xml" |
| "errors" |
| "fmt" |
| "io" |
| "net/http" |
| "net/url" |
| "time" |
| ) |
| |
| // GetPageRangesResponse contains the response fields from |
| // Get Page Ranges call. |
| // |
| // See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx |
| type GetPageRangesResponse struct { |
| XMLName xml.Name `xml:"PageList"` |
| PageList []PageRange `xml:"PageRange"` |
| } |
| |
| // PageRange contains information about a page of a page blob from |
| // Get Pages Range call. |
| // |
| // See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx |
| type PageRange struct { |
| Start int64 `xml:"Start"` |
| End int64 `xml:"End"` |
| } |
| |
| var ( |
| errBlobCopyAborted = errors.New("storage: blob copy is aborted") |
| errBlobCopyIDMismatch = errors.New("storage: blob copy id is a mismatch") |
| ) |
| |
| // PutPageOptions includes the options for a put page operation |
| type PutPageOptions struct { |
| Timeout uint |
| LeaseID string `header:"x-ms-lease-id"` |
| IfSequenceNumberLessThanOrEqualTo *int `header:"x-ms-if-sequence-number-le"` |
| IfSequenceNumberLessThan *int `header:"x-ms-if-sequence-number-lt"` |
| IfSequenceNumberEqualTo *int `header:"x-ms-if-sequence-number-eq"` |
| IfModifiedSince *time.Time `header:"If-Modified-Since"` |
| IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"` |
| IfMatch string `header:"If-Match"` |
| IfNoneMatch string `header:"If-None-Match"` |
| RequestID string `header:"x-ms-client-request-id"` |
| } |
| |
| // WriteRange writes a range of pages to a page blob. |
| // Ranges must be aligned with 512-byte boundaries and chunk must be of size |
| // multiplies by 512. |
| // |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Page |
| func (b *Blob) WriteRange(blobRange BlobRange, bytes io.Reader, options *PutPageOptions) error { |
| if bytes == nil { |
| return errors.New("bytes cannot be nil") |
| } |
| return b.modifyRange(blobRange, bytes, options) |
| } |
| |
| // ClearRange clears the given range in a page blob. |
| // Ranges must be aligned with 512-byte boundaries and chunk must be of size |
| // multiplies by 512. |
| // |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Page |
| func (b *Blob) ClearRange(blobRange BlobRange, options *PutPageOptions) error { |
| return b.modifyRange(blobRange, nil, options) |
| } |
| |
| func (b *Blob) modifyRange(blobRange BlobRange, bytes io.Reader, options *PutPageOptions) error { |
| if blobRange.End < blobRange.Start { |
| return errors.New("the value for rangeEnd must be greater than or equal to rangeStart") |
| } |
| if blobRange.Start%512 != 0 { |
| return errors.New("the value for rangeStart must be a multiple of 512") |
| } |
| if blobRange.End%512 != 511 { |
| return errors.New("the value for rangeEnd must be a multiple of 512 - 1") |
| } |
| |
| params := url.Values{"comp": {"page"}} |
| |
| // default to clear |
| write := "clear" |
| var cl uint64 |
| |
| // if bytes is not nil then this is an update operation |
| if bytes != nil { |
| write = "update" |
| cl = (blobRange.End - blobRange.Start) + 1 |
| } |
| |
| headers := b.Container.bsc.client.getStandardHeaders() |
| headers["x-ms-blob-type"] = string(BlobTypePage) |
| headers["x-ms-page-write"] = write |
| headers["x-ms-range"] = blobRange.String() |
| headers["Content-Length"] = fmt.Sprintf("%v", cl) |
| |
| if options != nil { |
| params = addTimeout(params, options.Timeout) |
| headers = mergeHeaders(headers, headersFromStruct(*options)) |
| } |
| uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params) |
| |
| resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, bytes, b.Container.bsc.auth) |
| if err != nil { |
| return err |
| } |
| defer drainRespBody(resp) |
| return checkRespCode(resp, []int{http.StatusCreated}) |
| } |
| |
| // GetPageRangesOptions includes the options for a get page ranges operation |
| type GetPageRangesOptions struct { |
| Timeout uint |
| Snapshot *time.Time |
| PreviousSnapshot *time.Time |
| Range *BlobRange |
| LeaseID string `header:"x-ms-lease-id"` |
| RequestID string `header:"x-ms-client-request-id"` |
| } |
| |
| // GetPageRanges returns the list of valid page ranges for a page blob. |
| // |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Get-Page-Ranges |
| func (b *Blob) GetPageRanges(options *GetPageRangesOptions) (GetPageRangesResponse, error) { |
| params := url.Values{"comp": {"pagelist"}} |
| headers := b.Container.bsc.client.getStandardHeaders() |
| |
| if options != nil { |
| params = addTimeout(params, options.Timeout) |
| params = addSnapshot(params, options.Snapshot) |
| if options.PreviousSnapshot != nil { |
| params.Add("prevsnapshot", timeRFC3339Formatted(*options.PreviousSnapshot)) |
| } |
| if options.Range != nil { |
| headers["Range"] = options.Range.String() |
| } |
| headers = mergeHeaders(headers, headersFromStruct(*options)) |
| } |
| uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params) |
| |
| var out GetPageRangesResponse |
| resp, err := b.Container.bsc.client.exec(http.MethodGet, uri, headers, nil, b.Container.bsc.auth) |
| if err != nil { |
| return out, err |
| } |
| defer drainRespBody(resp) |
| |
| if err = checkRespCode(resp, []int{http.StatusOK}); err != nil { |
| return out, err |
| } |
| err = xmlUnmarshal(resp.Body, &out) |
| return out, err |
| } |
| |
| // PutPageBlob initializes an empty page blob with specified name and maximum |
| // size in bytes (size must be aligned to a 512-byte boundary). A page blob must |
| // be created using this method before writing pages. |
| // |
| // See CreateBlockBlobFromReader for more info on creating blobs. |
| // |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Blob |
| func (b *Blob) PutPageBlob(options *PutBlobOptions) error { |
| if b.Properties.ContentLength%512 != 0 { |
| return errors.New("Content length must be aligned to a 512-byte boundary") |
| } |
| |
| params := url.Values{} |
| headers := b.Container.bsc.client.getStandardHeaders() |
| headers["x-ms-blob-type"] = string(BlobTypePage) |
| headers["x-ms-blob-content-length"] = fmt.Sprintf("%v", b.Properties.ContentLength) |
| headers["x-ms-blob-sequence-number"] = fmt.Sprintf("%v", b.Properties.SequenceNumber) |
| headers = mergeHeaders(headers, headersFromStruct(b.Properties)) |
| headers = b.Container.bsc.client.addMetadataToHeaders(headers, b.Metadata) |
| |
| if options != nil { |
| params = addTimeout(params, options.Timeout) |
| headers = mergeHeaders(headers, headersFromStruct(*options)) |
| } |
| uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params) |
| |
| resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth) |
| if err != nil { |
| return err |
| } |
| return b.respondCreation(resp, BlobTypePage) |
| } |