...

Source file src/github.com/Azure/azure-sdk-for-go/storage/client_test.go

Documentation: github.com/Azure/azure-sdk-for-go/storage

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

View as plain text