1package storage 2 3// Copyright 2017 Microsoft Corporation 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use this file except in compliance with the License. 7// You may obtain a copy of the License at 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, 13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14// See the License for the specific language governing permissions and 15// limitations under the License. 16 17import ( 18 "errors" 19 "net/http" 20 "net/url" 21 "strconv" 22 "time" 23) 24 25// lease constants. 26const ( 27 leaseHeaderPrefix = "x-ms-lease-" 28 headerLeaseID = "x-ms-lease-id" 29 leaseAction = "x-ms-lease-action" 30 leaseBreakPeriod = "x-ms-lease-break-period" 31 leaseDuration = "x-ms-lease-duration" 32 leaseProposedID = "x-ms-proposed-lease-id" 33 leaseTime = "x-ms-lease-time" 34 35 acquireLease = "acquire" 36 renewLease = "renew" 37 changeLease = "change" 38 releaseLease = "release" 39 breakLease = "break" 40) 41 42// leasePut is common PUT code for the various acquire/release/break etc functions. 43func (b *Blob) leaseCommonPut(headers map[string]string, expectedStatus int, options *LeaseOptions) (http.Header, error) { 44 params := url.Values{"comp": {"lease"}} 45 46 if options != nil { 47 params = addTimeout(params, options.Timeout) 48 headers = mergeHeaders(headers, headersFromStruct(*options)) 49 } 50 uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params) 51 52 resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth) 53 if err != nil { 54 return nil, err 55 } 56 defer drainRespBody(resp) 57 58 if err := checkRespCode(resp, []int{expectedStatus}); err != nil { 59 return nil, err 60 } 61 62 return resp.Header, nil 63} 64 65// LeaseOptions includes options for all operations regarding leasing blobs 66type LeaseOptions struct { 67 Timeout uint 68 Origin string `header:"Origin"` 69 IfMatch string `header:"If-Match"` 70 IfNoneMatch string `header:"If-None-Match"` 71 IfModifiedSince *time.Time `header:"If-Modified-Since"` 72 IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"` 73 RequestID string `header:"x-ms-client-request-id"` 74} 75 76// AcquireLease creates a lease for a blob 77// returns leaseID acquired 78// In API Versions starting on 2012-02-12, the minimum leaseTimeInSeconds is 15, the maximum 79// non-infinite leaseTimeInSeconds is 60. To specify an infinite lease, provide the value -1. 80// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob 81func (b *Blob) AcquireLease(leaseTimeInSeconds int, proposedLeaseID string, options *LeaseOptions) (returnedLeaseID string, err error) { 82 headers := b.Container.bsc.client.getStandardHeaders() 83 headers[leaseAction] = acquireLease 84 85 if leaseTimeInSeconds == -1 { 86 // Do nothing, but don't trigger the following clauses. 87 } else if leaseTimeInSeconds > 60 || b.Container.bsc.client.apiVersion < "2012-02-12" { 88 leaseTimeInSeconds = 60 89 } else if leaseTimeInSeconds < 15 { 90 leaseTimeInSeconds = 15 91 } 92 93 headers[leaseDuration] = strconv.Itoa(leaseTimeInSeconds) 94 95 if proposedLeaseID != "" { 96 headers[leaseProposedID] = proposedLeaseID 97 } 98 99 respHeaders, err := b.leaseCommonPut(headers, http.StatusCreated, options) 100 if err != nil { 101 return "", err 102 } 103 104 returnedLeaseID = respHeaders.Get(http.CanonicalHeaderKey(headerLeaseID)) 105 106 if returnedLeaseID != "" { 107 return returnedLeaseID, nil 108 } 109 110 return "", errors.New("LeaseID not returned") 111} 112 113// BreakLease breaks the lease for a blob 114// Returns the timeout remaining in the lease in seconds 115// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob 116func (b *Blob) BreakLease(options *LeaseOptions) (breakTimeout int, err error) { 117 headers := b.Container.bsc.client.getStandardHeaders() 118 headers[leaseAction] = breakLease 119 return b.breakLeaseCommon(headers, options) 120} 121 122// BreakLeaseWithBreakPeriod breaks the lease for a blob 123// breakPeriodInSeconds is used to determine how long until new lease can be created. 124// Returns the timeout remaining in the lease in seconds 125// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob 126func (b *Blob) BreakLeaseWithBreakPeriod(breakPeriodInSeconds int, options *LeaseOptions) (breakTimeout int, err error) { 127 headers := b.Container.bsc.client.getStandardHeaders() 128 headers[leaseAction] = breakLease 129 headers[leaseBreakPeriod] = strconv.Itoa(breakPeriodInSeconds) 130 return b.breakLeaseCommon(headers, options) 131} 132 133// breakLeaseCommon is common code for both version of BreakLease (with and without break period) 134func (b *Blob) breakLeaseCommon(headers map[string]string, options *LeaseOptions) (breakTimeout int, err error) { 135 136 respHeaders, err := b.leaseCommonPut(headers, http.StatusAccepted, options) 137 if err != nil { 138 return 0, err 139 } 140 141 breakTimeoutStr := respHeaders.Get(http.CanonicalHeaderKey(leaseTime)) 142 if breakTimeoutStr != "" { 143 breakTimeout, err = strconv.Atoi(breakTimeoutStr) 144 if err != nil { 145 return 0, err 146 } 147 } 148 149 return breakTimeout, nil 150} 151 152// ChangeLease changes a lease ID for a blob 153// Returns the new LeaseID acquired 154// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob 155func (b *Blob) ChangeLease(currentLeaseID string, proposedLeaseID string, options *LeaseOptions) (newLeaseID string, err error) { 156 headers := b.Container.bsc.client.getStandardHeaders() 157 headers[leaseAction] = changeLease 158 headers[headerLeaseID] = currentLeaseID 159 headers[leaseProposedID] = proposedLeaseID 160 161 respHeaders, err := b.leaseCommonPut(headers, http.StatusOK, options) 162 if err != nil { 163 return "", err 164 } 165 166 newLeaseID = respHeaders.Get(http.CanonicalHeaderKey(headerLeaseID)) 167 if newLeaseID != "" { 168 return newLeaseID, nil 169 } 170 171 return "", errors.New("LeaseID not returned") 172} 173 174// ReleaseLease releases the lease for a blob 175// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob 176func (b *Blob) ReleaseLease(currentLeaseID string, options *LeaseOptions) error { 177 headers := b.Container.bsc.client.getStandardHeaders() 178 headers[leaseAction] = releaseLease 179 headers[headerLeaseID] = currentLeaseID 180 181 _, err := b.leaseCommonPut(headers, http.StatusOK, options) 182 if err != nil { 183 return err 184 } 185 186 return nil 187} 188 189// RenewLease renews the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx 190func (b *Blob) RenewLease(currentLeaseID string, options *LeaseOptions) error { 191 headers := b.Container.bsc.client.getStandardHeaders() 192 headers[leaseAction] = renewLease 193 headers[headerLeaseID] = currentLeaseID 194 195 _, err := b.leaseCommonPut(headers, http.StatusOK, options) 196 if err != nil { 197 return err 198 } 199 200 return nil 201} 202