PageRenderTime 108ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/github.com/Azure/azure-sdk-for-go/storage/client_test.go

https://gitlab.com/unofficial-mirrors/openshift-origin
Go | 661 lines | 521 code | 99 blank | 41 comment | 64 complexity | 6ab95b751d7891904c79087e9e128806 MD5 | raw file
  1. package storage
  2. // Copyright 2017 Microsoft Corporation
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. import (
  16. "bytes"
  17. "encoding/base64"
  18. "io/ioutil"
  19. "math"
  20. "net/http"
  21. "net/url"
  22. "os"
  23. "path/filepath"
  24. "reflect"
  25. "regexp"
  26. "strconv"
  27. "strings"
  28. "testing"
  29. "time"
  30. "github.com/Azure/go-autorest/autorest/azure"
  31. "github.com/dnaeon/go-vcr/cassette"
  32. "github.com/dnaeon/go-vcr/recorder"
  33. chk "gopkg.in/check.v1"
  34. )
  35. // Hook up gocheck to testing
  36. func Test(t *testing.T) { chk.TestingT(t) }
  37. type StorageClientSuite struct{}
  38. var _ = chk.Suite(&StorageClientSuite{})
  39. func setHeaders(haystack http.Header, predicate func(string) bool, value string) {
  40. for key := range haystack {
  41. if predicate(key) {
  42. haystack[key] = []string{value}
  43. }
  44. }
  45. }
  46. func deleteHeaders(haystack http.Header, predicate func(string) bool) {
  47. for key := range haystack {
  48. if predicate(key) {
  49. delete(haystack, key)
  50. }
  51. }
  52. }
  53. func getHeaders(haystack http.Header, predicate func(string) bool) string {
  54. for key, value := range haystack {
  55. if predicate(key) && len(value) > 0 {
  56. return value[0]
  57. }
  58. }
  59. return ""
  60. }
  61. // getBasicClient returns a test client from storage credentials in the env
  62. func getBasicClient(c *chk.C) *Client {
  63. name := os.Getenv("ACCOUNT_NAME")
  64. if name == "" {
  65. name = dummyStorageAccount
  66. }
  67. key := os.Getenv("ACCOUNT_KEY")
  68. if key == "" {
  69. key = dummyMiniStorageKey
  70. }
  71. cli, err := NewBasicClient(name, key)
  72. c.Assert(err, chk.IsNil)
  73. return &cli
  74. }
  75. func (client *Client) appendRecorder(c *chk.C) *recorder.Recorder {
  76. tests := strings.Split(c.TestName(), ".")
  77. path := filepath.Join(recordingsFolder, tests[0], tests[1])
  78. if overwriteRec {
  79. fullPath := filepath.Join(pwd, path+".yaml")
  80. _, err := os.Stat(fullPath)
  81. if err == nil {
  82. err := os.Remove(fullPath)
  83. c.Assert(err, chk.IsNil)
  84. }
  85. }
  86. rec, err := recorder.New(path)
  87. c.Assert(err, chk.IsNil)
  88. client.HTTPClient = &http.Client{
  89. Transport: rec,
  90. }
  91. rec.SetMatcher(func(r *http.Request, i cassette.Request) bool {
  92. return compareMethods(r, i) &&
  93. compareURLs(r, i) &&
  94. compareHeaders(r, i) &&
  95. compareBodies(r, i)
  96. })
  97. return rec
  98. }
  99. func (client *Client) usesDummies() bool {
  100. key, err := base64.StdEncoding.DecodeString(dummyMiniStorageKey)
  101. if err != nil {
  102. return false
  103. }
  104. if string(client.accountKey) == string(key) &&
  105. client.accountName == dummyStorageAccount {
  106. return true
  107. }
  108. return false
  109. }
  110. func compareMethods(r *http.Request, i cassette.Request) bool {
  111. return r.Method == i.Method
  112. }
  113. func compareURLs(r *http.Request, i cassette.Request) bool {
  114. requestURL := modifyURL(r.URL)
  115. cassetteURL, err := url.Parse(i.URL)
  116. if err != nil {
  117. return false
  118. }
  119. err = removeSAS(cassetteURL)
  120. if err != nil {
  121. return false
  122. }
  123. return requestURL.String() == cassetteURL.String()
  124. }
  125. func modifyURL(uri *url.URL) *url.URL {
  126. // The URL host looks like this...
  127. // accountname.service.storageEndpointSuffix
  128. parts := strings.Split(uri.Host, ".")
  129. // parts[0] corresponds to the storage account name, so it can be (almost) any string
  130. // parts[1] corresponds to the service name (table, blob, etc.).
  131. if !(parts[1] == blobServiceName ||
  132. parts[1] == tableServiceName ||
  133. parts[1] == queueServiceName ||
  134. parts[1] == fileServiceName) {
  135. return nil
  136. }
  137. // The rest of the host depends on which Azure cloud is used
  138. storageEndpointSuffix := strings.Join(parts[2:], ".")
  139. if !(storageEndpointSuffix == azure.PublicCloud.StorageEndpointSuffix ||
  140. storageEndpointSuffix == azure.USGovernmentCloud.StorageEndpointSuffix ||
  141. storageEndpointSuffix == azure.ChinaCloud.StorageEndpointSuffix ||
  142. storageEndpointSuffix == azure.GermanCloud.StorageEndpointSuffix) {
  143. return nil
  144. }
  145. host := dummyStorageAccount + "." + parts[1] + "." + azure.PublicCloud.StorageEndpointSuffix
  146. newURL := uri
  147. newURL.Host = host
  148. err := removeSAS(newURL)
  149. if err != nil {
  150. return nil
  151. }
  152. return newURL
  153. }
  154. func removeSAS(uri *url.URL) error {
  155. // Remove signature from a SAS URI
  156. v := uri.Query()
  157. v.Del("sig")
  158. q, err := url.QueryUnescape(v.Encode())
  159. if err != nil {
  160. return err
  161. }
  162. uri.RawQuery = q
  163. return nil
  164. }
  165. func compareHeaders(r *http.Request, i cassette.Request) bool {
  166. requestHeaders := r.Header
  167. cassetteHeaders := i.Headers
  168. // Some headers shall not be compared...
  169. getHeaderMatchPredicate := func(needle string) func(string) bool {
  170. return func(straw string) bool {
  171. return strings.EqualFold(needle, straw)
  172. }
  173. }
  174. isUserAgent := getHeaderMatchPredicate("User-Agent")
  175. isAuthorization := getHeaderMatchPredicate("Authorization")
  176. isDate := getHeaderMatchPredicate("x-ms-date")
  177. deleteHeaders(requestHeaders, isUserAgent)
  178. deleteHeaders(requestHeaders, isAuthorization)
  179. deleteHeaders(requestHeaders, isDate)
  180. deleteHeaders(cassetteHeaders, isUserAgent)
  181. deleteHeaders(cassetteHeaders, isAuthorization)
  182. deleteHeaders(cassetteHeaders, isDate)
  183. isCopySource := getHeaderMatchPredicate("X-Ms-Copy-Source")
  184. srcURLstr := getHeaders(requestHeaders, isCopySource)
  185. if srcURLstr != "" {
  186. srcURL, err := url.Parse(srcURLstr)
  187. if err != nil {
  188. return false
  189. }
  190. modifiedURL := modifyURL(srcURL)
  191. setHeaders(requestHeaders, isCopySource, modifiedURL.String())
  192. }
  193. // Do not compare the complete Content-Type header in table batch requests
  194. if isBatchOp(r.URL.String()) {
  195. // They all start like this, but then they have a UUID...
  196. ctPrefixBatch := "multipart/mixed; boundary=batch_"
  197. isContentType := getHeaderMatchPredicate("Content-Type")
  198. contentTypeRequest := getHeaders(requestHeaders, isContentType)
  199. contentTypeCassette := getHeaders(cassetteHeaders, isContentType)
  200. if !(strings.HasPrefix(contentTypeRequest, ctPrefixBatch) &&
  201. strings.HasPrefix(contentTypeCassette, ctPrefixBatch)) {
  202. return false
  203. }
  204. deleteHeaders(requestHeaders, isContentType)
  205. deleteHeaders(cassetteHeaders, isContentType)
  206. }
  207. return reflect.DeepEqual(requestHeaders, cassetteHeaders)
  208. }
  209. func compareBodies(r *http.Request, i cassette.Request) bool {
  210. body := bytes.Buffer{}
  211. if r.Body != nil {
  212. _, err := body.ReadFrom(r.Body)
  213. if err != nil {
  214. return false
  215. }
  216. r.Body = ioutil.NopCloser(&body)
  217. }
  218. // Comparing bodies in table batch operations is trickier, because the bodies include UUIDs
  219. if isBatchOp(r.URL.String()) {
  220. return compareBatchBodies(body.String(), i.Body)
  221. }
  222. return body.String() == i.Body
  223. }
  224. func compareBatchBodies(rBody, cBody string) bool {
  225. // UUIDs in the batch body look like this...
  226. // 2d7f2323-1e42-11e7-8c6c-6451064d81e8
  227. exp, err := regexp.Compile("[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}")
  228. if err != nil {
  229. return false
  230. }
  231. rBody = replaceStorageAccount(replaceUUIDs(rBody, exp))
  232. cBody = replaceUUIDs(cBody, exp)
  233. return rBody == cBody
  234. }
  235. func replaceUUIDs(body string, exp *regexp.Regexp) string {
  236. indexes := exp.FindAllStringIndex(body, -1)
  237. for _, pair := range indexes {
  238. body = strings.Replace(body, body[pair[0]:pair[1]], "00000000-0000-0000-0000-000000000000", -1)
  239. }
  240. return body
  241. }
  242. func isBatchOp(url string) bool {
  243. return url == "https://golangrocksonazure.table.core.windows.net/$batch"
  244. }
  245. //getEmulatorClient returns a test client for Azure Storeage Emulator
  246. func getEmulatorClient(c *chk.C) Client {
  247. cli, err := NewBasicClient(StorageEmulatorAccountName, "")
  248. c.Assert(err, chk.IsNil)
  249. return cli
  250. }
  251. func (s *StorageClientSuite) TestNewEmulatorClient(c *chk.C) {
  252. cli, err := NewBasicClient(StorageEmulatorAccountName, "")
  253. c.Assert(err, chk.IsNil)
  254. c.Assert(cli.accountName, chk.Equals, StorageEmulatorAccountName)
  255. expectedKey, err := base64.StdEncoding.DecodeString(StorageEmulatorAccountKey)
  256. c.Assert(err, chk.IsNil)
  257. c.Assert(cli.accountKey, chk.DeepEquals, expectedKey)
  258. }
  259. func (s *StorageClientSuite) TestNewFromConnectionString(c *chk.C) {
  260. cli, err := NewClientFromConnectionString(
  261. "DefaultEndpointsProtocol=https;" +
  262. "AccountName=myaccountname;" +
  263. "AccountKey=" + StorageEmulatorAccountKey + ";" +
  264. "EndpointSuffix=example.com")
  265. c.Assert(err, chk.IsNil)
  266. c.Assert(cli.accountName, chk.Equals, "myaccountname")
  267. expectedKey, err := base64.StdEncoding.DecodeString(StorageEmulatorAccountKey)
  268. c.Assert(err, chk.IsNil)
  269. c.Assert(cli.accountKey, chk.DeepEquals, expectedKey)
  270. }
  271. func (s *StorageClientSuite) TestNewAccountSASClientFromEndpointToken(c *chk.C) {
  272. cli, err := NewAccountSASClientFromEndpointToken(
  273. "http://golangrocksonazure.blob.core.windows.net",
  274. "sv=2017-04-17&ss=bq&srt=sco&sp=rwdlacup&se=2018-01-09T22:32:27Z&st=2018-01-08T14:32:27Z&spr=http&sig=z8K9AiNvsQAoRQmqEgHrk3KdRfY37MxCHckGi%2BJRFDI%3D",
  275. )
  276. c.Assert(err, chk.IsNil)
  277. c.Assert(cli.accountSASToken, chk.HasLen, 8)
  278. c.Assert(cli.accountName, chk.Equals, "golangrocksonazure")
  279. c.Assert(cli.baseURL, chk.Equals, "core.windows.net")
  280. c.Assert(cli.apiVersion, chk.Equals, "2017-04-17")
  281. c.Assert(cli.useHTTPS, chk.Equals, false)
  282. }
  283. func (s *StorageClientSuite) TestIsValidStorageAccount(c *chk.C) {
  284. type test struct {
  285. account string
  286. expected bool
  287. }
  288. testCases := []test{
  289. {"name1", true},
  290. {"Name2", false},
  291. {"reallyLongName1234567891011", false},
  292. {"", false},
  293. {"concated&name", false},
  294. {"formatted name", false},
  295. }
  296. for _, tc := range testCases {
  297. c.Assert(IsValidStorageAccount(tc.account), chk.Equals, tc.expected)
  298. }
  299. }
  300. func (s *StorageClientSuite) TestMalformedKeyError(c *chk.C) {
  301. _, err := NewBasicClient(dummyStorageAccount, "malformed")
  302. c.Assert(err, chk.ErrorMatches, "azure: malformed storage account key: .*")
  303. }
  304. func (s *StorageClientSuite) TestGetBaseURL_Basic_Https(c *chk.C) {
  305. cli, err := NewBasicClient(dummyStorageAccount, dummyMiniStorageKey)
  306. c.Assert(err, chk.IsNil)
  307. c.Assert(cli.apiVersion, chk.Equals, DefaultAPIVersion)
  308. c.Assert(err, chk.IsNil)
  309. c.Assert(cli.getBaseURL("table").String(), chk.Equals, "https://golangrocksonazure.table.core.windows.net")
  310. }
  311. func (s *StorageClientSuite) TestGetBaseURL_Custom_NoHttps(c *chk.C) {
  312. apiVersion := "2015-01-01" // a non existing one
  313. cli, err := NewClient(dummyStorageAccount, dummyMiniStorageKey, "core.chinacloudapi.cn", apiVersion, false)
  314. c.Assert(err, chk.IsNil)
  315. c.Assert(cli.apiVersion, chk.Equals, apiVersion)
  316. c.Assert(cli.getBaseURL("table").String(), chk.Equals, "http://golangrocksonazure.table.core.chinacloudapi.cn")
  317. }
  318. func (s *StorageClientSuite) TestGetBaseURL_StorageEmulator(c *chk.C) {
  319. cli, err := NewBasicClient(StorageEmulatorAccountName, StorageEmulatorAccountKey)
  320. c.Assert(err, chk.IsNil)
  321. type test struct{ service, expected string }
  322. tests := []test{
  323. {blobServiceName, "http://127.0.0.1:10000"},
  324. {tableServiceName, "http://127.0.0.1:10002"},
  325. {queueServiceName, "http://127.0.0.1:10001"},
  326. }
  327. for _, i := range tests {
  328. baseURL := cli.getBaseURL(i.service)
  329. c.Assert(baseURL.String(), chk.Equals, i.expected)
  330. }
  331. }
  332. func (s *StorageClientSuite) TestGetEndpoint_None(c *chk.C) {
  333. cli, err := NewBasicClient(dummyStorageAccount, "YmFy")
  334. c.Assert(err, chk.IsNil)
  335. output := cli.getEndpoint(blobServiceName, "", url.Values{})
  336. c.Assert(output, chk.Equals, "https://golangrocksonazure.blob.core.windows.net/")
  337. }
  338. func (s *StorageClientSuite) TestGetEndpoint_PathOnly(c *chk.C) {
  339. cli, err := NewBasicClient(dummyStorageAccount, "YmFy")
  340. c.Assert(err, chk.IsNil)
  341. output := cli.getEndpoint(blobServiceName, "path", url.Values{})
  342. c.Assert(output, chk.Equals, "https://golangrocksonazure.blob.core.windows.net/path")
  343. }
  344. func (s *StorageClientSuite) TestGetEndpoint_ParamsOnly(c *chk.C) {
  345. cli, err := NewBasicClient(dummyStorageAccount, "YmFy")
  346. c.Assert(err, chk.IsNil)
  347. params := url.Values{}
  348. params.Set("a", "b")
  349. params.Set("c", "d")
  350. output := cli.getEndpoint(blobServiceName, "", params)
  351. c.Assert(output, chk.Equals, "https://golangrocksonazure.blob.core.windows.net/?a=b&c=d")
  352. }
  353. func (s *StorageClientSuite) TestGetEndpoint_Mixed(c *chk.C) {
  354. cli, err := NewBasicClient(dummyStorageAccount, "YmFy")
  355. c.Assert(err, chk.IsNil)
  356. params := url.Values{}
  357. params.Set("a", "b")
  358. params.Set("c", "d")
  359. output := cli.getEndpoint(blobServiceName, "path", params)
  360. c.Assert(output, chk.Equals, "https://golangrocksonazure.blob.core.windows.net/path?a=b&c=d")
  361. }
  362. func (s *StorageClientSuite) TestGetEndpoint_StorageEmulator(c *chk.C) {
  363. cli, err := NewBasicClient(StorageEmulatorAccountName, StorageEmulatorAccountKey)
  364. c.Assert(err, chk.IsNil)
  365. type test struct{ service, expected string }
  366. tests := []test{
  367. {blobServiceName, "http://127.0.0.1:10000/devstoreaccount1/"},
  368. {tableServiceName, "http://127.0.0.1:10002/devstoreaccount1/"},
  369. {queueServiceName, "http://127.0.0.1:10001/devstoreaccount1/"},
  370. }
  371. for _, i := range tests {
  372. endpoint := cli.getEndpoint(i.service, "", url.Values{})
  373. c.Assert(endpoint, chk.Equals, i.expected)
  374. }
  375. }
  376. func (s *StorageClientSuite) Test_getStandardHeaders(c *chk.C) {
  377. cli, err := NewBasicClient(dummyStorageAccount, "YmFy")
  378. c.Assert(err, chk.IsNil)
  379. headers := cli.getStandardHeaders()
  380. c.Assert(len(headers), chk.Equals, 3)
  381. c.Assert(headers["x-ms-version"], chk.Equals, cli.apiVersion)
  382. if _, ok := headers["x-ms-date"]; !ok {
  383. c.Fatal("Missing date header")
  384. }
  385. c.Assert(headers[userAgentHeader], chk.Equals, cli.getDefaultUserAgent())
  386. }
  387. func (s *StorageClientSuite) TestReturnsStorageServiceError(c *chk.C) {
  388. // attempt to delete nonexisting resources
  389. cli := getBasicClient(c)
  390. rec := cli.appendRecorder(c)
  391. defer rec.Stop()
  392. // XML response
  393. blobCli := cli.GetBlobService()
  394. cnt := blobCli.GetContainerReference(containerName(c))
  395. err := cnt.Delete(nil)
  396. c.Assert(err, chk.NotNil)
  397. v, ok := err.(AzureStorageServiceError)
  398. c.Check(ok, chk.Equals, true)
  399. c.Assert(v.StatusCode, chk.Equals, 404)
  400. c.Assert(v.Code, chk.Equals, "ContainerNotFound")
  401. c.Assert(v.RequestID, chk.Not(chk.Equals), "")
  402. c.Assert(v.Date, chk.Not(chk.Equals), "")
  403. c.Assert(v.APIVersion, chk.Not(chk.Equals), "")
  404. // JSON response
  405. tableCli := cli.GetTableService()
  406. table := tableCli.GetTableReference(tableName(c))
  407. err = table.Delete(30, nil)
  408. c.Assert(err, chk.NotNil)
  409. v, ok = err.(AzureStorageServiceError)
  410. c.Check(ok, chk.Equals, true)
  411. c.Assert(v.StatusCode, chk.Equals, 404)
  412. c.Assert(v.Code, chk.Equals, "ResourceNotFound")
  413. c.Assert(v.RequestID, chk.Not(chk.Equals), "")
  414. c.Assert(v.Date, chk.Not(chk.Equals), "")
  415. c.Assert(v.APIVersion, chk.Not(chk.Equals), "")
  416. }
  417. func (s *StorageClientSuite) TestReturnsStorageServiceError_withoutResponseBody(c *chk.C) {
  418. // HEAD on non-existing blob
  419. cli := getBlobClient(c)
  420. rec := cli.client.appendRecorder(c)
  421. defer rec.Stop()
  422. cnt := cli.GetContainerReference("non-existing-container")
  423. b := cnt.GetBlobReference("non-existing-blob")
  424. err := b.GetProperties(nil)
  425. c.Assert(err, chk.NotNil)
  426. c.Assert(err, chk.FitsTypeOf, AzureStorageServiceError{})
  427. v, ok := err.(AzureStorageServiceError)
  428. c.Check(ok, chk.Equals, true)
  429. c.Assert(v.StatusCode, chk.Equals, http.StatusNotFound)
  430. c.Assert(v.Code, chk.Equals, "404 The specified container does not exist.")
  431. c.Assert(v.RequestID, chk.Not(chk.Equals), "")
  432. c.Assert(v.Message, chk.Equals, "no response body was available for error status code")
  433. }
  434. func (s *StorageClientSuite) Test_createServiceClients(c *chk.C) {
  435. cli, err := NewBasicClient(dummyStorageAccount, "YmFy")
  436. c.Assert(err, chk.IsNil)
  437. ua := cli.getDefaultUserAgent()
  438. headers := cli.getStandardHeaders()
  439. c.Assert(headers[userAgentHeader], chk.Equals, ua)
  440. c.Assert(cli.userAgent, chk.Equals, ua)
  441. b := cli.GetBlobService()
  442. c.Assert(b.client.userAgent, chk.Equals, ua+" "+blobServiceName)
  443. c.Assert(cli.userAgent, chk.Equals, ua)
  444. t := cli.GetTableService()
  445. c.Assert(t.client.userAgent, chk.Equals, ua+" "+tableServiceName)
  446. c.Assert(cli.userAgent, chk.Equals, ua)
  447. q := cli.GetQueueService()
  448. c.Assert(q.client.userAgent, chk.Equals, ua+" "+queueServiceName)
  449. c.Assert(cli.userAgent, chk.Equals, ua)
  450. f := cli.GetFileService()
  451. c.Assert(f.client.userAgent, chk.Equals, ua+" "+fileServiceName)
  452. c.Assert(cli.userAgent, chk.Equals, ua)
  453. }
  454. func (s *StorageClientSuite) TestAddToUserAgent(c *chk.C) {
  455. cli, err := NewBasicClient(dummyStorageAccount, "YmFy")
  456. c.Assert(err, chk.IsNil)
  457. ua := cli.getDefaultUserAgent()
  458. err = cli.AddToUserAgent("rofl")
  459. c.Assert(err, chk.IsNil)
  460. c.Assert(cli.userAgent, chk.Equals, ua+" rofl")
  461. err = cli.AddToUserAgent("")
  462. c.Assert(err, chk.NotNil)
  463. }
  464. func (s *StorageClientSuite) Test_protectUserAgent(c *chk.C) {
  465. extraheaders := map[string]string{
  466. "1": "one",
  467. "2": "two",
  468. "3": "three",
  469. userAgentHeader: "four",
  470. }
  471. cli, err := NewBasicClient(dummyStorageAccount, "YmFy")
  472. c.Assert(err, chk.IsNil)
  473. ua := cli.getDefaultUserAgent()
  474. got := cli.protectUserAgent(extraheaders)
  475. c.Assert(cli.userAgent, chk.Equals, ua+" four")
  476. c.Assert(got, chk.HasLen, 3)
  477. c.Assert(got, chk.DeepEquals, map[string]string{
  478. "1": "one",
  479. "2": "two",
  480. "3": "three",
  481. })
  482. }
  483. func (s *StorageClientSuite) Test_doRetry(c *chk.C) {
  484. cli := getBasicClient(c)
  485. rec := cli.appendRecorder(c)
  486. defer rec.Stop()
  487. // Prepare request that will fail with 404 (delete non extising table)
  488. uri, err := url.Parse(cli.getEndpoint(tableServiceName, "(retry)", url.Values{"timeout": {strconv.Itoa(30)}}))
  489. c.Assert(err, chk.IsNil)
  490. req := http.Request{
  491. Method: http.MethodDelete,
  492. URL: uri,
  493. Header: http.Header{
  494. "Accept": {"application/json;odata=nometadata"},
  495. "Prefer": {"return-no-content"},
  496. "X-Ms-Version": {"2016-05-31"},
  497. },
  498. }
  499. ds, ok := cli.Sender.(*DefaultSender)
  500. c.Assert(ok, chk.Equals, true)
  501. // Modify sender so it retries quickly
  502. ds.RetryAttempts = 3
  503. ds.RetryDuration = time.Second
  504. // include 404 as a valid status code for retries
  505. ds.ValidStatusCodes = []int{http.StatusNotFound}
  506. cli.Sender = ds
  507. now := time.Now()
  508. resp, err := cli.Sender.Send(cli, &req)
  509. afterRetries := time.Since(now)
  510. c.Assert(err, chk.IsNil)
  511. c.Assert(resp.StatusCode, chk.Equals, http.StatusNotFound)
  512. // Was it the correct amount of retries... ?
  513. c.Assert(cli.Sender.(*DefaultSender).attempts, chk.Equals, ds.RetryAttempts)
  514. // What about time... ?
  515. // Note, seconds are rounded
  516. sum := 0
  517. for i := 0; i < ds.RetryAttempts; i++ {
  518. sum += int(ds.RetryDuration.Seconds() * math.Pow(2, float64(i))) // same formula used in autorest.DelayForBackoff
  519. }
  520. c.Assert(int(afterRetries.Seconds()), chk.Equals, sum)
  521. // Service SAS test
  522. blobCli := getBlobClient(c)
  523. cnt := blobCli.GetContainerReference(containerName(c))
  524. sasuriOptions := ContainerSASOptions{}
  525. sasuriOptions.Expiry = fixedTime
  526. sasuriOptions.Read = true
  527. sasuriOptions.Add = true
  528. sasuriOptions.Create = true
  529. sasuriOptions.Write = true
  530. sasuriOptions.Delete = true
  531. sasuriOptions.List = true
  532. sasuriString, err := cnt.GetSASURI(sasuriOptions)
  533. c.Assert(err, chk.IsNil)
  534. sasuri, err := url.Parse(sasuriString)
  535. c.Assert(err, chk.IsNil)
  536. cntServiceSAS, err := GetContainerReferenceFromSASURI(*sasuri)
  537. c.Assert(err, chk.IsNil)
  538. cntServiceSAS.Client().HTTPClient = cli.HTTPClient
  539. // This request will fail with 403
  540. ds.ValidStatusCodes = []int{http.StatusForbidden}
  541. cntServiceSAS.Client().Sender = ds
  542. now = time.Now()
  543. _, err = cntServiceSAS.Exists()
  544. afterRetries = time.Since(now)
  545. c.Assert(err, chk.NotNil)
  546. c.Assert(cntServiceSAS.Client().Sender.(*DefaultSender).attempts, chk.Equals, ds.RetryAttempts)
  547. c.Assert(int(afterRetries.Seconds()), chk.Equals, sum)
  548. // Account SAS test
  549. token, err := cli.GetAccountSASToken(accountSASOptions)
  550. c.Assert(err, chk.IsNil)
  551. SAScli := NewAccountSASClient(cli.accountName, token, azure.PublicCloud).GetBlobService()
  552. cntAccountSAS := SAScli.GetContainerReference(cnt.Name)
  553. cntAccountSAS.Client().HTTPClient = cli.HTTPClient
  554. ds.ValidStatusCodes = []int{http.StatusNotFound}
  555. cntAccountSAS.Client().Sender = ds
  556. now = time.Now()
  557. _, err = cntAccountSAS.Exists()
  558. afterRetries = time.Since(now)
  559. c.Assert(err, chk.IsNil)
  560. c.Assert(cntAccountSAS.Client().Sender.(*DefaultSender).attempts, chk.Equals, ds.RetryAttempts)
  561. c.Assert(int(afterRetries.Seconds()), chk.Equals, sum)
  562. }