diff --git a/cmd/api-errors.go b/cmd/api-errors.go index c899ce2d0..3d334d9c5 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -1138,14 +1138,14 @@ var errorCodes = errorCodeMap{ HTTPStatusCode: http.StatusBadRequest, }, ErrOperationTimedOut: { - Code: "XMinioServerTimedOut", - Description: "A timeout occurred while trying to lock a resource", - HTTPStatusCode: http.StatusRequestTimeout, + Code: "RequestTimeout", + Description: "A timeout occurred while trying to lock a resource, please reduce your request rate", + HTTPStatusCode: http.StatusServiceUnavailable, }, ErrOperationMaxedOut: { - Code: "XMinioServerTimedOut", - Description: "A timeout exceeded while waiting to proceed with the request", - HTTPStatusCode: http.StatusRequestTimeout, + Code: "SlowDown", + Description: "A timeout exceeded while waiting to proceed with the request, please reduce your request rate", + HTTPStatusCode: http.StatusServiceUnavailable, }, ErrUnsupportedMetadata: { Code: "InvalidArgument", diff --git a/cmd/format-fs.go b/cmd/format-fs.go index a300cd87d..1fab6dc62 100644 --- a/cmd/format-fs.go +++ b/cmd/format-fs.go @@ -27,6 +27,7 @@ import ( "github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/lock" + "github.com/minio/minio/pkg/retry" ) // FS format version strings. @@ -344,7 +345,7 @@ func formatFSFixDeploymentID(ctx context.Context, fsFormatPath string) error { defer cancel() var wlk *lock.LockedFile - retryCh := newRetryTimerSimple(retryCtx) + retryCh := retry.NewTimerWithJitter(retryCtx, time.Second, 30*time.Second, retry.MaxJitter) var stop bool for !stop { select { diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 234aec94b..efeec4645 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -613,13 +613,11 @@ func (fs *FSObjects) GetObjectNInfo(ctx context.Context, bucket, object string, switch lockType { case writeLock: if err = lock.GetLock(globalObjectTimeout); err != nil { - logger.LogIf(ctx, err) return nil, err } nsUnlocker = lock.Unlock case readLock: if err = lock.GetRLock(globalObjectTimeout); err != nil { - logger.LogIf(ctx, err) return nil, err } nsUnlocker = lock.RUnlock diff --git a/cmd/lock-rest-client.go b/cmd/lock-rest-client.go index 4af32cba6..08f0662a8 100644 --- a/cmd/lock-rest-client.go +++ b/cmd/lock-rest-client.go @@ -76,7 +76,7 @@ func (client *lockRESTClient) call(method string, values url.Values, body io.Rea } if isNetworkError(err) { - time.AfterFunc(defaultRetryUnit, func() { + time.AfterFunc(time.Second, func() { // After 1 seconds, take this lock client online for a retry. atomic.StoreInt32(&client.connected, 1) }) diff --git a/cmd/namespace-lock.go b/cmd/namespace-lock.go index c7d48cce6..23f0b32a2 100644 --- a/cmd/namespace-lock.go +++ b/cmd/namespace-lock.go @@ -28,9 +28,9 @@ import ( "fmt" "time" - "github.com/minio/lsync" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/dsync" + "github.com/minio/minio/pkg/lsync" ) // local lock servers diff --git a/cmd/server-main.go b/cmd/server-main.go index fafe4fbb9..549881758 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -38,6 +38,7 @@ import ( "github.com/minio/minio/pkg/certs" "github.com/minio/minio/pkg/color" "github.com/minio/minio/pkg/env" + "github.com/minio/minio/pkg/retry" ) // ServerFlags - server command specific flags @@ -216,10 +217,10 @@ func initSafeMode() (err error) { // version is needed, migration is needed etc. rquorum := InsufficientReadQuorum{} wquorum := InsufficientWriteQuorum{} - for range newRetryTimerSimple(retryCtx) { + for range retry.NewTimer(retryCtx) { // let one of the server acquire the lock, if not let them timeout. // which shall be retried again by this loop. - if err = txnLk.GetLock(leaderLockTimeout); err != nil { + if err = txnLk.GetLock(newDynamicTimeout(5*time.Second, 30*time.Second)); err != nil { logger.Info("Waiting for all MinIO sub-systems to be initialized.. trying to acquire lock") continue } diff --git a/cmd/server_test.go b/cmd/server_test.go index 0445f076a..df59bf654 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -18,8 +18,6 @@ package cmd import ( "bytes" - "crypto/tls" - "crypto/x509" "encoding/xml" "fmt" "io" @@ -47,7 +45,7 @@ type TestSuiteCommon struct { secretKey string signer signerType secure bool - transport *http.Transport + client *http.Client } type check struct { @@ -85,7 +83,6 @@ func runAllTests(suite *TestSuiteCommon, c *check) { suite.TestEmptyObject(c) suite.TestBucket(c) suite.TestObjectGetAnonymous(c) - suite.TestObjectGet(c) suite.TestMultipleObjects(c) suite.TestHeader(c) suite.TestPutBucket(c) @@ -147,21 +144,11 @@ func (s *TestSuiteCommon) SetUpSuite(c *check) { c.Assert(err, nil) s.testServer = StartTestTLSServer(c, s.serverType, cert, key) - - rootCAs := x509.NewCertPool() - rootCAs.AppendCertsFromPEM(cert) - tlsConfig := &tls.Config{ - RootCAs: rootCAs, - } - tlsConfig.BuildNameToCertificate() - - s.transport = &http.Transport{ - TLSClientConfig: tlsConfig, - } } else { s.testServer = StartTestServer(c, s.serverType) - s.transport = &http.Transport{} } + + s.client = s.testServer.Server.Client() s.endPoint = s.testServer.Server.URL s.accessKey = s.testServer.AccessKey s.secretKey = s.testServer.SecretKey @@ -182,9 +169,8 @@ func (s *TestSuiteCommon) TestBucketSQSNotificationWebHook(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the http response status code. @@ -194,9 +180,8 @@ func (s *TestSuiteCommon) TestBucketSQSNotificationWebHook(c *check) { int64(len(bucketNotificationBuf)), bytes.NewReader([]byte(bucketNotificationBuf)), s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) verifyError(c, response, "InvalidArgument", "A specified destination ARN does not exist or is not well-formed. Verify the destination ARN.", http.StatusBadRequest) @@ -209,9 +194,8 @@ func (s *TestSuiteCommon) TestObjectDir(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the http response status code. @@ -221,9 +205,8 @@ func (s *TestSuiteCommon) TestObjectDir(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the http response status code. @@ -237,9 +220,8 @@ func (s *TestSuiteCommon) TestObjectDir(c *check) { request.ContentLength = helloReader.Size() request.Body = ioutil.NopCloser(helloReader) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) verifyError(c, response, "XMinioInvalidObjectName", "Object name contains unsupported characters.", http.StatusBadRequest) @@ -248,9 +230,8 @@ func (s *TestSuiteCommon) TestObjectDir(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -259,9 +240,8 @@ func (s *TestSuiteCommon) TestObjectDir(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -270,9 +250,8 @@ func (s *TestSuiteCommon) TestObjectDir(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusNoContent) @@ -288,9 +267,8 @@ func (s *TestSuiteCommon) TestBucketSQSNotificationAMQP(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the http response status code. @@ -300,9 +278,8 @@ func (s *TestSuiteCommon) TestBucketSQSNotificationAMQP(c *check) { int64(len(bucketNotificationBuf)), bytes.NewReader([]byte(bucketNotificationBuf)), s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) verifyError(c, response, "InvalidArgument", "A specified destination ARN does not exist or is not well-formed. Verify the destination ARN.", http.StatusBadRequest) @@ -323,9 +300,8 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the http response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -335,9 +311,8 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *check) { int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusNoContent) @@ -346,8 +321,7 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *check) { s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -365,8 +339,7 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *check) { s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusNoContent) @@ -375,8 +348,7 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusNotFound) } @@ -390,8 +362,7 @@ func (s *TestSuiteCommon) TestDeleteBucket(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -401,8 +372,7 @@ func (s *TestSuiteCommon) TestDeleteBucket(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Assert the response status code. c.Assert(response.StatusCode, http.StatusNoContent) @@ -418,9 +388,8 @@ func (s *TestSuiteCommon) TestDeleteBucketNotEmpty(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -432,9 +401,8 @@ func (s *TestSuiteCommon) TestDeleteBucketNotEmpty(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the request to complete object upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the status code of the response. c.Assert(response.StatusCode, http.StatusOK) @@ -446,8 +414,7 @@ func (s *TestSuiteCommon) TestDeleteBucketNotEmpty(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusConflict) @@ -461,9 +428,8 @@ func (s *TestSuiteCommon) TestListenBucketNotificationHandler(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the request. - response, err := client.Do(req) + response, err := s.client.Do(req) c.Assert(err, nil) // assert the http response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -479,9 +445,8 @@ func (s *TestSuiteCommon) TestListenBucketNotificationHandler(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the request. - response, err = client.Do(req) + response, err = s.client.Do(req) c.Assert(err, nil) verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest) @@ -490,9 +455,8 @@ func (s *TestSuiteCommon) TestListenBucketNotificationHandler(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the request. - response, err = client.Do(req) + response, err = s.client.Do(req) c.Assert(err, nil) verifyError(c, response, "InvalidArgument", "A specified event is not supported for notifications.", http.StatusBadRequest) @@ -501,9 +465,8 @@ func (s *TestSuiteCommon) TestListenBucketNotificationHandler(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the request. - response, err = client.Do(req) + response, err = s.client.Do(req) c.Assert(err, nil) verifyError(c, response, "InvalidArgument", "Size of filter rule value cannot exceed 1024 bytes in UTF-8 representation", http.StatusBadRequest) @@ -512,9 +475,8 @@ func (s *TestSuiteCommon) TestListenBucketNotificationHandler(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the request. - response, err = client.Do(req) + response, err = s.client.Do(req) c.Assert(err, nil) if s.signer == signerV4 { verifyError(c, response, "XAmzContentSHA256Mismatch", "The provided 'x-amz-content-sha256' header does not match what was computed.", http.StatusBadRequest) @@ -530,9 +492,8 @@ func (s *TestSuiteCommon) TestDeleteMultipleObjects(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the http response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -549,9 +510,8 @@ func (s *TestSuiteCommon) TestDeleteMultipleObjects(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the http request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the status of http response. c.Assert(response.StatusCode, http.StatusOK) @@ -568,8 +528,7 @@ func (s *TestSuiteCommon) TestDeleteMultipleObjects(c *check) { request, err = newTestSignedRequest("POST", getMultiDeleteObjectURL(s.endPoint, bucketName), int64(len(deleteReqBytes)), bytes.NewReader(deleteReqBytes), s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -589,8 +548,7 @@ func (s *TestSuiteCommon) TestDeleteMultipleObjects(c *check) { request, err = newTestSignedRequest("POST", getMultiDeleteObjectURL(s.endPoint, bucketName), int64(len(deleteReqBytes)), bytes.NewReader(deleteReqBytes), s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -615,9 +573,8 @@ func (s *TestSuiteCommon) TestDeleteObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the http response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -629,9 +586,8 @@ func (s *TestSuiteCommon) TestDeleteObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the http request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the status of http response. c.Assert(response.StatusCode, http.StatusOK) @@ -641,8 +597,7 @@ func (s *TestSuiteCommon) TestDeleteObject(c *check) { request, err = newTestSignedRequest("DELETE", getDeleteObjectURL(s.endPoint, bucketName, "prefix"), 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusNoContent) @@ -652,8 +607,7 @@ func (s *TestSuiteCommon) TestDeleteObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Assert the HTTP response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -662,9 +616,8 @@ func (s *TestSuiteCommon) TestDeleteObject(c *check) { request, err = newTestSignedRequest("DELETE", getDeleteObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the http request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the http response status code. c.Assert(response.StatusCode, http.StatusNoContent) @@ -673,9 +626,8 @@ func (s *TestSuiteCommon) TestDeleteObject(c *check) { request, err = newTestSignedRequest("DELETE", getDeleteObjectURL(s.endPoint, bucketName, "prefix/myobject1"), 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the http request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the http response status. c.Assert(response.StatusCode, http.StatusNoContent) @@ -691,9 +643,8 @@ func (s *TestSuiteCommon) TestNonExistentBucket(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the http request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // Assert the response. c.Assert(response.StatusCode, http.StatusNotFound) @@ -708,9 +659,8 @@ func (s *TestSuiteCommon) TestEmptyObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the http request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the http response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -721,9 +671,8 @@ func (s *TestSuiteCommon) TestEmptyObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the upload request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the http response. c.Assert(response.StatusCode, http.StatusOK) @@ -733,9 +682,8 @@ func (s *TestSuiteCommon) TestEmptyObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the http request to fetch object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the http response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -756,8 +704,7 @@ func (s *TestSuiteCommon) TestBucket(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -765,8 +712,7 @@ func (s *TestSuiteCommon) TestBucket(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) } @@ -781,9 +727,8 @@ func (s *TestSuiteCommon) TestObjectGetAnonymous(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the make bucket http request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // assert the response http status code. c.Assert(response.StatusCode, http.StatusOK) @@ -794,86 +739,25 @@ func (s *TestSuiteCommon) TestObjectGetAnonymous(c *check) { int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to upload the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the HTTP response status code. c.Assert(response.StatusCode, http.StatusOK) // initiate anonymous HTTP request to fetch the object which does not exist. We need to return AccessDenied. - response, err = client.Get(getGetObjectURL(s.endPoint, bucketName, objectName+".1")) + response, err = s.client.Get(getGetObjectURL(s.endPoint, bucketName, objectName+".1")) c.Assert(err, nil) // assert the http response status code. verifyError(c, response, "AccessDenied", "Access Denied.", http.StatusForbidden) // initiate anonymous HTTP request to fetch the object which does exist. We need to return AccessDenied. - response, err = client.Get(getGetObjectURL(s.endPoint, bucketName, objectName)) + response, err = s.client.Get(getGetObjectURL(s.endPoint, bucketName, objectName)) c.Assert(err, nil) // assert the http response status code. verifyError(c, response, "AccessDenied", "Access Denied.", http.StatusForbidden) } -// TestGetObject - Tests fetching of a small object after its insertion into the bucket. -func (s *TestSuiteCommon) TestObjectGet(c *check) { - // generate a random bucket name. - bucketName := getRandomBucketName() - buffer := bytes.NewReader([]byte("hello world")) - // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), - 0, nil, s.accessKey, s.secretKey, s.signer) - c.Assert(err, nil) - - client := http.Client{Transport: s.transport} - // execute the make bucket http request. - response, err := client.Do(request) - c.Assert(err, nil) - // assert the response http status code. - c.Assert(response.StatusCode, http.StatusOK) - - objectName := "testObject" - // create HTTP request to upload the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), - int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer) - c.Assert(err, nil) - - client = http.Client{Transport: s.transport} - // execute the HTTP request to upload the object. - response, err = client.Do(request) - c.Assert(err, nil) - // assert the HTTP response status code. - c.Assert(response.StatusCode, http.StatusOK) - // concurrently reading the object, safety check for races. - var wg sync.WaitGroup - for i := 0; i < testConcurrencyLevel; i++ { - wg.Add(1) - go func() { - defer wg.Done() - // HTTP request to create the bucket. - // create HTTP request to fetch the object. - getRequest, err := newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), - 0, nil, s.accessKey, s.secretKey, s.signer) - c.Assert(err, nil) - - reqClient := http.Client{Transport: s.transport} - // execute the http request to fetch the object. - getResponse, err := reqClient.Do(getRequest) - c.Assert(err, nil) - defer getResponse.Body.Close() - // assert the http response status code. - c.Assert(getResponse.StatusCode, http.StatusOK) - - // extract response body content. - responseBody, err := ioutil.ReadAll(getResponse.Body) - c.Assert(err, nil) - // assert the HTTP response body content with the expected content. - c.Assert(responseBody, []byte("hello world")) - }() - - } - wg.Wait() -} - // TestMultipleObjects - Validates upload and fetching of multiple object into the bucket. func (s *TestSuiteCommon) TestMultipleObjects(c *check) { // generate a random bucket name. @@ -883,9 +767,8 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create the bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -896,9 +779,8 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Asserting the error response with the expected values. verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound) @@ -911,9 +793,8 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) { int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request for object upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the returned values. c.Assert(response.StatusCode, http.StatusOK) @@ -923,9 +804,8 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert whether 200 OK response status is obtained. c.Assert(response.StatusCode, http.StatusOK) @@ -943,9 +823,8 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) { int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request for object upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the response status code for expected value 200 OK. c.Assert(response.StatusCode, http.StatusOK) @@ -954,9 +833,8 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to fetch the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // assert the response status code for expected value 200 OK. c.Assert(response.StatusCode, http.StatusOK) @@ -973,9 +851,8 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) { int64(buffer3.Len()), buffer3, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // verify the response code with the expected value of 200 OK. c.Assert(response.StatusCode, http.StatusOK) @@ -985,8 +862,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1005,8 +881,7 @@ func (s *TestSuiteCommon) TestHeader(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // asserting for the expected error response. verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist", http.StatusNotFound) @@ -1029,8 +904,7 @@ func (s *TestSuiteCommon) TestPutBucket(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} - response, err := client.Do(request) + response, err := s.client.Do(request) if err != nil { c.Errorf("Put bucket Failed: %s", err) return @@ -1047,8 +921,7 @@ func (s *TestSuiteCommon) TestPutBucket(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) response.Body.Close() @@ -1068,9 +941,8 @@ func (s *TestSuiteCommon) TestCopyObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1087,7 +959,7 @@ func (s *TestSuiteCommon) TestCopyObject(c *check) { } c.Assert(err, nil) // execute the HTTP request for object upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1106,7 +978,7 @@ func (s *TestSuiteCommon) TestCopyObject(c *check) { c.Assert(err, nil) // execute the HTTP request. // the content is expected to have the content of previous disk. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1115,7 +987,7 @@ func (s *TestSuiteCommon) TestCopyObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // executing the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // validating the response status code. c.Assert(response.StatusCode, http.StatusOK) @@ -1135,9 +1007,8 @@ func (s *TestSuiteCommon) TestPutObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1149,7 +1020,7 @@ func (s *TestSuiteCommon) TestPutObject(c *check) { int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request for object upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1158,7 +1029,7 @@ func (s *TestSuiteCommon) TestPutObject(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request to fetch the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) c.Assert(response.ContentLength, int64(len([]byte("hello world")))) @@ -1185,7 +1056,7 @@ func (s *TestSuiteCommon) TestPutObject(c *check) { } c.Assert(err, nil) // execute the HTTP request for object upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) // The response Etag header should contain Md5sum of an empty string. @@ -1202,9 +1073,8 @@ func (s *TestSuiteCommon) TestListBuckets(c *check) { request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to list buckets. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1213,9 +1083,8 @@ func (s *TestSuiteCommon) TestListBuckets(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to list buckets. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1254,9 +1123,8 @@ func (s *TestSuiteCommon) TestValidateSignature(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // Execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1268,7 +1136,7 @@ func (s *TestSuiteCommon) TestValidateSignature(c *check) { secretKey := s.secretKey + "a" request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objName), 0, nil, s.accessKey, secretKey, s.signer) c.Assert(err, nil) - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) verifyError(c, response, "SignatureDoesNotMatch", "The request signature we calculated does not match the signature you provided. Check your key and signing method.", http.StatusForbidden) } @@ -1282,9 +1150,8 @@ func (s *TestSuiteCommon) TestSHA256Mismatch(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // Execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1302,8 +1169,9 @@ func (s *TestSuiteCommon) TestSHA256Mismatch(c *check) { request.ContentLength = helloReader.Size() request.Body = ioutil.NopCloser(helloReader) c.Assert(err, nil) + // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) if s.signer == signerV4 { verifyError(c, response, "XAmzContentSHA256Mismatch", "The provided 'x-amz-content-sha256' header does not match what was computed.", http.StatusBadRequest) @@ -1320,9 +1188,8 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // Execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) // Content for the object to be uploaded. @@ -1337,7 +1204,7 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *check) { int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1352,7 +1219,7 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *check) { int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusBadRequest) verifyError(c, response, "KeyTooLongError", "Your key is too long", http.StatusBadRequest) @@ -1365,7 +1232,7 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *check) { int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusBadRequest) verifyError(c, response, "XMinioInvalidObjectName", "Object name contains a leading slash.", http.StatusBadRequest) @@ -1377,7 +1244,7 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *check) { int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) verifyError(c, response, "XMinioInvalidObjectName", "Object name contains unsupported characters.", http.StatusBadRequest) } @@ -1396,9 +1263,8 @@ func (s *TestSuiteCommon) TestNotBeAbleToCreateObjectInNonexistentBucket(c *chec int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // Execute the HTTP request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // Assert the response error message. verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist", http.StatusNotFound) @@ -1418,9 +1284,8 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1434,7 +1299,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *check) { c.Assert(err, nil) // executing the HTTP request to download the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) // make HTTP request to obtain object info. @@ -1442,7 +1307,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // verify the status of the HTTP response. c.Assert(response.StatusCode, http.StatusOK) @@ -1460,7 +1325,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) request.Header.Set("If-Modified-Since", t.Add(10*time.Minute).UTC().Format(http.TimeFormat)) - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Since the "If-Modified-Since" header was ahead in time compared to the actual // modified time of the object expecting the response status to be http.StatusNotModified. @@ -1473,7 +1338,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) request.Header.Set("If-Unmodified-Since", t.Add(-10*time.Minute).UTC().Format(http.TimeFormat)) - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusPreconditionFailed) @@ -1483,7 +1348,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) request.Header.Set("If-Unmodified-Since", "Mon, 02 Jan 2006 15:04:05 +00:00") - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Since the "If-Modified-Since" header was ahead in time compared to the actual // modified time of the object expecting the response status to be http.StatusNotModified. @@ -1501,9 +1366,8 @@ func (s *TestSuiteCommon) TestHeadOnBucket(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) // make HEAD request on the bucket. @@ -1511,7 +1375,7 @@ func (s *TestSuiteCommon) TestHeadOnBucket(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Asserting the response status for expected value of http.StatusOK. c.Assert(response.StatusCode, http.StatusOK) @@ -1527,9 +1391,8 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1547,9 +1410,8 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *check) { c.Assert(err, nil) } - client = http.Client{Transport: s.transport} // execute the HTTP request for object upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1559,7 +1421,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *check) { c.Assert(err, nil) // Execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Verify if the Content-Type header is set during the object persists. c.Assert(response.Header.Get("Content-Type"), "image/png") @@ -1569,9 +1431,8 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // Execute the HTTP to fetch the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) // Verify if the Content-Type header is set during the object persists. @@ -1591,7 +1452,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *check) { } // Execute the HTTP request to upload the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1600,7 +1461,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // Execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Assert if the content-type header set during the object upload persists. c.Assert(response.Header.Get("Content-Type"), "application/json") @@ -1611,7 +1472,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *check) { c.Assert(err, nil) // Execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Assert if the content-type header set during the object upload persists. c.Assert(response.Header.Get("Content-Type"), "application/json") @@ -1627,8 +1488,7 @@ func (s *TestSuiteCommon) TestPartialContent(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1637,8 +1497,7 @@ func (s *TestSuiteCommon) TestPartialContent(c *check) { int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1648,8 +1507,7 @@ func (s *TestSuiteCommon) TestPartialContent(c *check) { c.Assert(err, nil) request.Header.Add("Range", "bytes=6-7") - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusPartialContent) partialObject, err := ioutil.ReadAll(response.Body) @@ -1668,9 +1526,8 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1680,8 +1537,7 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *check) { int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) } @@ -1713,9 +1569,8 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *check) { // create listObjectsV1 request with valid parameters request, err = newTestSignedRequest("GET", testCase.getURL, 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1738,9 +1593,8 @@ func (s *TestSuiteCommon) TestListObjectsHandlerErrors(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1748,9 +1602,8 @@ func (s *TestSuiteCommon) TestListObjectsHandlerErrors(c *check) { request, err = newTestSignedRequest("GET", getListObjectsV1URL(s.endPoint, bucketName, "", "-2", ""), 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // validating the error response. verifyError(c, response, "InvalidArgument", "Argument maxKeys must be an integer between 0 and 2147483647", http.StatusBadRequest) @@ -1759,9 +1612,8 @@ func (s *TestSuiteCommon) TestListObjectsHandlerErrors(c *check) { request, err = newTestSignedRequest("GET", getListObjectsV2URL(s.endPoint, bucketName, "", "-2", "", ""), 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // validating the error response. verifyError(c, response, "InvalidArgument", "Argument maxKeys must be an integer between 0 and 2147483647", http.StatusBadRequest) @@ -1779,8 +1631,7 @@ func (s *TestSuiteCommon) TestPutBucketErrors(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) // expected to fail with error message "InvalidBucketName". verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest) @@ -1789,9 +1640,8 @@ func (s *TestSuiteCommon) TestPutBucketErrors(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) // make HTTP request to create the same bucket again. @@ -1800,7 +1650,7 @@ func (s *TestSuiteCommon) TestPutBucketErrors(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) verifyError(c, response, "BucketAlreadyOwnedByYou", "Your previous request to create the named bucket succeeded and you already own it.", http.StatusConflict) @@ -1814,9 +1664,8 @@ func (s *TestSuiteCommon) TestGetObjectLarge10MiB(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create the bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1844,9 +1693,8 @@ func (s *TestSuiteCommon) TestGetObjectLarge10MiB(c *check) { int64(buf.Len()), buf, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Assert the status code to verify successful upload. c.Assert(response.StatusCode, http.StatusOK) @@ -1856,9 +1704,8 @@ func (s *TestSuiteCommon) TestGetObjectLarge10MiB(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to download the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) // extract the content from response body. @@ -1878,9 +1725,8 @@ func (s *TestSuiteCommon) TestGetObjectLarge11MiB(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1909,9 +1755,8 @@ func (s *TestSuiteCommon) TestGetObjectLarge11MiB(c *check) { int64(buf.Len()), buf, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request for object upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1920,9 +1765,8 @@ func (s *TestSuiteCommon) TestGetObjectLarge11MiB(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) // fetch the content from response body. @@ -1947,9 +1791,8 @@ func (s *TestSuiteCommon) TestGetPartialObjectMisAligned(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create the bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -1977,9 +1820,8 @@ func (s *TestSuiteCommon) TestGetPartialObjectMisAligned(c *check) { int64(buf.Len()), buf, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to upload the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -2010,9 +1852,8 @@ func (s *TestSuiteCommon) TestGetPartialObjectMisAligned(c *check) { // Get partial content based on the byte range set. request.Header.Add("Range", "bytes="+t.byteRange) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Since only part of the object is requested, expecting response status to be http.StatusPartialContent . c.Assert(response.StatusCode, http.StatusPartialContent) @@ -2034,9 +1875,8 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge11MiB(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create the bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -2066,9 +1906,8 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge11MiB(c *check) { int64(buf.Len()), buf, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to upload the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -2079,9 +1918,8 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge11MiB(c *check) { // This range spans into first two blocks. request.Header.Add("Range", "bytes=10485750-10485769") - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Since only part of the object is requested, expecting response status to be http.StatusPartialContent . c.Assert(response.StatusCode, http.StatusPartialContent) @@ -2102,9 +1940,8 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge10MiB(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) // expecting the error to be nil. c.Assert(err, nil) // expecting the HTTP response status code to 200 OK. @@ -2135,9 +1972,8 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge10MiB(c *check) { int64(buf.Len()), buf, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to upload the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // verify whether upload was successful. c.Assert(response.StatusCode, http.StatusOK) @@ -2149,9 +1985,8 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge10MiB(c *check) { // Get partial content based on the byte range set. request.Header.Add("Range", "bytes=2048-2058") - client = http.Client{Transport: s.transport} // execute the HTTP request to download the partial content. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Since only part of the object is requested, expecting response status to be http.StatusPartialContent . c.Assert(response.StatusCode, http.StatusPartialContent) @@ -2173,9 +2008,8 @@ func (s *TestSuiteCommon) TestGetObjectErrors(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -2187,8 +2021,7 @@ func (s *TestSuiteCommon) TestGetObjectErrors(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound) @@ -2198,7 +2031,7 @@ func (s *TestSuiteCommon) TestGetObjectErrors(c *check) { c.Assert(err, nil) // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // expected to fail with "InvalidBucketName". verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest) @@ -2213,9 +2046,8 @@ func (s *TestSuiteCommon) TestGetObjectRangeErrors(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -2228,9 +2060,8 @@ func (s *TestSuiteCommon) TestGetObjectRangeErrors(c *check) { int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to upload the object. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // verify whether upload was successful. c.Assert(response.StatusCode, http.StatusOK) @@ -2242,9 +2073,8 @@ func (s *TestSuiteCommon) TestGetObjectRangeErrors(c *check) { request.Header.Add("Range", "bytes=-0") c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // expected to fail with "InvalidRange" error message. verifyError(c, response, "InvalidRange", "The requested range is not satisfiable", http.StatusRequestedRangeNotSatisfiable) @@ -2259,9 +2089,8 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -2280,7 +2109,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *check) { c.Assert(err, nil) // execute the HTTP request initiating the new multipart upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -2298,7 +2127,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *check) { c.Assert(err, nil) // execute the HTTP request initiating the new multipart upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -2319,7 +2148,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *check) { int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request to upload the first part. - response1, err := client.Do(request) + response1, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response1.StatusCode, http.StatusOK) @@ -2330,7 +2159,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *check) { int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request to upload the second part. - response2, err := client.Do(request) + response2, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response2.StatusCode, http.StatusOK) // HTTP request for aborting the multipart upload. @@ -2338,7 +2167,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request to abort the multipart upload. - response3, err := client.Do(request) + response3, err := s.client.Do(request) c.Assert(err, nil) // expecting the response status code to be http.StatusNoContent. // The assertion validates the success of Abort Multipart operation. @@ -2354,9 +2183,8 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *check) { nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, 200) @@ -2366,7 +2194,7 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request initiating the new multipart upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // expecting the response status code to be http.StatusOK(200 OK) . c.Assert(response.StatusCode, http.StatusOK) @@ -2388,7 +2216,7 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *check) { int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request to upload the first part. - response1, err := client.Do(request) + response1, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response1.StatusCode, http.StatusOK) @@ -2399,7 +2227,7 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *check) { int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request to upload the second part. - response2, err := client.Do(request) + response2, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response2.StatusCode, http.StatusOK) @@ -2408,7 +2236,7 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request. - response3, err := client.Do(request) + response3, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response3.StatusCode, http.StatusOK) @@ -2467,9 +2295,8 @@ func (s *TestSuiteCommon) TestValidateObjectMultipartUploadID(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, 200) @@ -2479,7 +2306,7 @@ func (s *TestSuiteCommon) TestValidateObjectMultipartUploadID(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request initiating the new multipart upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) @@ -2503,9 +2330,8 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, 200) @@ -2515,7 +2341,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request initiating the new multipart upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) // parse the response body and obtain the new upload ID. @@ -2535,7 +2361,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *check) { int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request to upload the first part. - response1, err := client.Do(request) + response1, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response1.StatusCode, http.StatusOK) @@ -2547,7 +2373,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *check) { c.Assert(err, nil) // execute the HTTP request to upload the second part. - response2, err := client.Do(request) + response2, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response2.StatusCode, http.StatusOK) @@ -2557,7 +2383,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request. - response3, err := client.Do(request) + response3, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response3.StatusCode, http.StatusOK) @@ -2567,7 +2393,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // execute the HTTP request. - response4, err := client.Do(request) + response4, err := s.client.Do(request) c.Assert(err, nil) // Since max-keys parameter in the ListMultipart request set to invalid value of -2, // its expected to fail with error message "InvalidArgument". @@ -2584,9 +2410,8 @@ func (s *TestSuiteCommon) TestObjectValidMD5(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, 200) @@ -2604,8 +2429,7 @@ func (s *TestSuiteCommon) TestObjectValidMD5(c *check) { c.Assert(err, nil) // set the Content-Md5 to be the hash to content. request.Header.Set("Content-Md5", etagBase64) - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // expecting a successful upload. c.Assert(response.StatusCode, http.StatusOK) @@ -2618,8 +2442,7 @@ func (s *TestSuiteCommon) TestObjectValidMD5(c *check) { // set Content-Md5 to invalid value. request.Header.Set("Content-Md5", "kvLTlMrX9NpYDQlEIFlnDA==") // expecting a failure during upload. - client = http.Client{Transport: s.transport} - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // Since Content-Md5 header was wrong, expecting to fail with "SignatureDoesNotMatch" error. verifyError(c, response, "SignatureDoesNotMatch", "The request signature we calculated does not match the signature you provided. Check your key and signing method.", http.StatusForbidden) @@ -2635,9 +2458,8 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client := http.Client{Transport: s.transport} // execute the HTTP request to create bucket. - response, err := client.Do(request) + response, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, 200) @@ -2647,9 +2469,8 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *check) { 0, nil, s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request initiating the new multipart upload. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // expecting the response status code to be http.StatusOK(200 OK). c.Assert(response.StatusCode, http.StatusOK) @@ -2677,9 +2498,8 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *check) { request.Header.Set("Content-Md5", md5SumBase64) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to upload the first part. - response1, err := client.Do(request) + response1, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response1.StatusCode, http.StatusOK) @@ -2698,9 +2518,8 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *check) { request.Header.Set("Content-Md5", md5SumBase64) c.Assert(err, nil) - client = http.Client{Transport: s.transport} // execute the HTTP request to upload the second part. - response2, err := client.Do(request) + response2, err := s.client.Do(request) c.Assert(err, nil) c.Assert(response2.StatusCode, http.StatusOK) @@ -2725,7 +2544,7 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *check) { int64(len(completeBytes)), bytes.NewReader(completeBytes), s.accessKey, s.secretKey, s.signer) c.Assert(err, nil) // Execute the complete multipart request. - response, err = client.Do(request) + response, err = s.client.Do(request) c.Assert(err, nil) // verify whether complete multipart was successful. c.Assert(response.StatusCode, http.StatusOK) diff --git a/go.mod b/go.mod index be709bf9a..53797a3e1 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,6 @@ require ( github.com/miekg/dns v1.1.8 github.com/minio/cli v1.22.0 github.com/minio/highwayhash v1.0.0 - github.com/minio/lsync v1.0.1 github.com/minio/minio-go/v6 v6.0.56 github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61 github.com/minio/sha256-simd v0.1.1 diff --git a/go.sum b/go.sum index 53bde7c15..476104ff9 100644 --- a/go.sum +++ b/go.sum @@ -273,8 +273,6 @@ github.com/minio/cli v1.22.0 h1:VTQm7lmXm3quxO917X3p+el1l0Ca5X3S4PM2ruUYO68= github.com/minio/cli v1.22.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY= github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA= github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= -github.com/minio/lsync v1.0.1 h1:AVvILxA976xc27hstd1oR+X9PQG0sPSom1MNb1ImfUs= -github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pArKA= github.com/minio/minio-go/v6 v6.0.53/go.mod h1:DIvC/IApeHX8q1BAMVCXSXwpmrmM+I+iBvhvztQorfI= github.com/minio/minio-go/v6 v6.0.56 h1:H4+v6UFV1V7VkEf1HjL15W9OvTL1Gy8EbMmjQZHqEbg= github.com/minio/minio-go/v6 v6.0.56/go.mod h1:KQMM+/44DSlSGSQWSfRrAZ12FVMmpWNuX37i2AX0jfI= diff --git a/pkg/dsync/drwmutex.go b/pkg/dsync/drwmutex.go index ca5364769..8614e85c3 100644 --- a/pkg/dsync/drwmutex.go +++ b/pkg/dsync/drwmutex.go @@ -24,6 +24,8 @@ import ( "os" "sync" "time" + + "github.com/minio/minio/pkg/retry" ) // Indicator if logging is enabled. @@ -128,49 +130,41 @@ func (dm *DRWMutex) GetRLock(id, source string, timeout time.Duration) (locked b // algorithm until either the lock is acquired successfully or more // time has elapsed than the timeout value. func (dm *DRWMutex) lockBlocking(timeout time.Duration, id, source string, isReadLock bool) (locked bool) { - start := time.Now().UTC() - restClnts := dm.clnt.GetLockersFn() - retryCtx, cancel := context.WithCancel(dm.ctx) + retryCtx, cancel := context.WithTimeout(dm.ctx, timeout) defer cancel() // Use incremental back-off algorithm for repeated attempts to acquire the lock - for range newRetryTimerSimple(retryCtx) { - select { - case <-dm.ctx.Done(): - return - default: - } - + for range retry.NewTimer(retryCtx) { // Create temp array on stack. locks := make([]string, len(restClnts)) // Try to acquire the lock. success := lock(dm.clnt, &locks, id, source, isReadLock, dm.Names...) - if success { - dm.m.Lock() - - // If success, copy array to object - if isReadLock { - // Append new array of strings at the end - dm.readersLocks = append(dm.readersLocks, make([]string, len(restClnts))) - // and copy stack array into last spot - copy(dm.readersLocks[len(dm.readersLocks)-1], locks[:]) - } else { - copy(dm.writeLocks, locks[:]) - } - - dm.m.Unlock() - return true + if !success { + continue } - if time.Now().UTC().Sub(start) >= timeout { // Are we past the timeout? - break + + dm.m.Lock() + + // If success, copy array to object + if isReadLock { + // Append new array of strings at the end + dm.readersLocks = append(dm.readersLocks, make([]string, len(restClnts))) + // and copy stack array into last spot + copy(dm.readersLocks[len(dm.readersLocks)-1], locks[:]) + } else { + copy(dm.writeLocks, locks[:]) } - // Failed to acquire the lock on this attempt, incrementally wait - // for a longer back-off time and try again afterwards. + + dm.m.Unlock() + return true } + + // Failed to acquire the lock on this attempt, incrementally wait + // for a longer back-off time and try again afterwards. return false } @@ -233,7 +227,7 @@ func lock(ds *Dsync, locks *[]string, id, source string, isReadLock bool, lockNa // // a) received all lock responses // b) received too many 'non-'locks for quorum to be still possible - // c) time out + // c) timedout // i, locksFailed := 0, 0 done := false diff --git a/pkg/dsync/retry.go b/pkg/dsync/retry.go deleted file mode 100644 index e1305a356..000000000 --- a/pkg/dsync/retry.go +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2017 Minio, Inc. - * - * 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. - */ - -package dsync - -import ( - "context" - "math/rand" - "sync" - "time" -) - -// lockedRandSource provides protected rand source, implements rand.Source interface. -type lockedRandSource struct { - lk sync.Mutex - src rand.Source -} - -// Int63 returns a non-negative pseudo-random 63-bit integer as an -// int64. -func (r *lockedRandSource) Int63() (n int64) { - r.lk.Lock() - n = r.src.Int63() - r.lk.Unlock() - return -} - -// Seed uses the provided seed value to initialize the generator to a -// deterministic state. -func (r *lockedRandSource) Seed(seed int64) { - r.lk.Lock() - r.src.Seed(seed) - r.lk.Unlock() -} - -// MaxJitter will randomize over the full exponential backoff time -const MaxJitter = 1.0 - -// NoJitter disables the use of jitter for randomizing the -// exponential backoff time -const NoJitter = 0.0 - -// Global random source for fetching random values. -var globalRandomSource = rand.New(&lockedRandSource{ - src: rand.NewSource(time.Now().UTC().UnixNano()), -}) - -// newRetryTimerJitter creates a timer with exponentially increasing delays -// until the maximum retry attempts are reached. - this function is a fully -// configurable version, meant for only advanced use cases. For the most part -// one should use newRetryTimerSimple and newRetryTimer. -func newRetryTimerWithJitter(ctx context.Context, unit time.Duration, cap time.Duration, jitter float64) <-chan int { - attemptCh := make(chan int) - - // normalize jitter to the range [0, 1.0] - if jitter < NoJitter { - jitter = NoJitter - } - if jitter > MaxJitter { - jitter = MaxJitter - } - - // computes the exponential backoff duration according to - // https://www.awsarchitectureblog.com/2015/03/backoff.html - exponentialBackoffWait := func(attempt int) time.Duration { - // 1< maxAttempt { - attempt = maxAttempt - } - //sleep = random_between(0, min(cap, base * 2 ** attempt)) - sleep := unit * time.Duration(1< cap { - sleep = cap - } - if jitter != NoJitter { - sleep -= time.Duration(globalRandomSource.Float64() * float64(sleep) * jitter) - } - return sleep - } - - go func() { - defer close(attemptCh) - nextBackoff := 0 - // Channel used to signal after the expiry of backoff wait seconds. - var timer *time.Timer - for { - select { // Attempts starts. - case attemptCh <- nextBackoff: - nextBackoff++ - case <-ctx.Done(): - // Stop the routine. - return - } - timer = time.NewTimer(exponentialBackoffWait(nextBackoff)) - // wait till next backoff time or till doneCh gets a message. - select { - case <-timer.C: - case <-ctx.Done(): - // stop the timer and return. - timer.Stop() - return - } - - } - }() - - // Start reading.. - return attemptCh -} - -// Default retry constants. -const ( - defaultRetryUnit = time.Second // 1 second. - defaultRetryCap = 1 * time.Second // 1 second. -) - -// newRetryTimerSimple creates a timer with exponentially increasing delays -// until the maximum retry attempts are reached. - this function is a -// simpler version with all default values. -func newRetryTimerSimple(ctx context.Context) <-chan int { - return newRetryTimerWithJitter(ctx, defaultRetryUnit, defaultRetryCap, MaxJitter) -} diff --git a/pkg/dsync/retry_test.go b/pkg/dsync/retry_test.go deleted file mode 100644 index 01387645c..000000000 --- a/pkg/dsync/retry_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2017 Minio, Inc. - * - * 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. - */ - -package dsync - -import ( - "context" - "testing" - "time" -) - -// Tests for retry timer. -func TestRetryTimerSimple(t *testing.T) { - rctx, cancel := context.WithCancel(context.Background()) - attemptCh := newRetryTimerSimple(rctx) - i := <-attemptCh - if i != 0 { - cancel() - t.Fatalf("Invalid attempt counter returned should be 0, found %d instead", i) - } - i = <-attemptCh - if i <= 0 { - cancel() - t.Fatalf("Invalid attempt counter returned should be greater than 0, found %d instead", i) - } - cancel() - _, ok := <-attemptCh - if ok { - t.Fatal("Attempt counter should be closed") - } -} - -// Test retry time with no jitter. -func TestRetryTimerWithNoJitter(t *testing.T) { - rctx, cancel := context.WithCancel(context.Background()) - - // No jitter - attemptCh := newRetryTimerWithJitter(rctx, time.Millisecond, 5*time.Millisecond, NoJitter) - i := <-attemptCh - if i != 0 { - cancel() - t.Fatalf("Invalid attempt counter returned should be 0, found %d instead", i) - } - // Loop through the maximum possible attempt. - for i = range attemptCh { - if i == 30 { - break - } - } - cancel() - - _, ok := <-attemptCh - if ok { - t.Fatal("Attempt counter should be closed") - } -} - -// Test retry time with Jitter greater than MaxJitter. -func TestRetryTimerWithJitter(t *testing.T) { - rctx, cancel := context.WithCancel(context.Background()) - - // Jitter will be set back to 1.0 - attemptCh := newRetryTimerWithJitter(rctx, time.Second, 30*time.Second, 2.0) - i := <-attemptCh - if i != 0 { - cancel() - t.Fatalf("Invalid attempt counter returned should be 0, found %d instead", i) - } - cancel() - _, ok := <-attemptCh - if ok { - t.Fatal("Attempt counter should be closed") - } -} diff --git a/pkg/lsync/lrwmutex.go b/pkg/lsync/lrwmutex.go new file mode 100644 index 000000000..787763834 --- /dev/null +++ b/pkg/lsync/lrwmutex.go @@ -0,0 +1,188 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * 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. + */ + +package lsync + +import ( + "context" + "math" + "sync" + "time" + + "github.com/minio/minio/pkg/retry" +) + +// A LRWMutex is a mutual exclusion lock with timeouts. +type LRWMutex struct { + id string + source string + isWriteLock bool + ref int + m sync.Mutex // Mutex to prevent multiple simultaneous locks + ctx context.Context +} + +// NewLRWMutex - initializes a new lsync RW mutex. +func NewLRWMutex(ctx context.Context) *LRWMutex { + return &LRWMutex{ctx: ctx} +} + +// Lock holds a write lock on lm. +// +// If the lock is already in use, the calling go routine +// blocks until the mutex is available. +func (lm *LRWMutex) Lock() { + + const isWriteLock = true + lm.lockLoop(lm.id, lm.source, time.Duration(math.MaxInt64), isWriteLock) +} + +// GetLock tries to get a write lock on lm before the timeout occurs. +func (lm *LRWMutex) GetLock(id string, source string, timeout time.Duration) (locked bool) { + + const isWriteLock = true + return lm.lockLoop(id, source, timeout, isWriteLock) +} + +// RLock holds a read lock on lm. +// +// If one or more read lock are already in use, it will grant another lock. +// Otherwise the calling go routine blocks until the mutex is available. +func (lm *LRWMutex) RLock() { + + const isWriteLock = false + lm.lockLoop(lm.id, lm.source, time.Duration(1<<63-1), isWriteLock) +} + +// GetRLock tries to get a read lock on lm before the timeout occurs. +func (lm *LRWMutex) GetRLock(id string, source string, timeout time.Duration) (locked bool) { + + const isWriteLock = false + return lm.lockLoop(id, source, timeout, isWriteLock) +} + +// lockLoop will acquire either a read or a write lock +// +// The call will block until the lock is granted using a built-in +// timing randomized back-off algorithm to try again until successful +func (lm *LRWMutex) lockLoop(id, source string, timeout time.Duration, isWriteLock bool) bool { + retryCtx, cancel := context.WithTimeout(lm.ctx, timeout) + + defer cancel() + + // We timed out on the previous lock, incrementally wait + // for a longer back-off time and try again afterwards. + for range retry.NewTimer(retryCtx) { + // Try to acquire the lock. + var success bool + { + lm.m.Lock() + + lm.id = id + lm.source = source + + if isWriteLock { + if lm.ref == 0 && !lm.isWriteLock { + lm.ref = 1 + lm.isWriteLock = true + success = true + } + } else { + if !lm.isWriteLock { + lm.ref++ + success = true + } + } + + lm.m.Unlock() + } + + if success { + return true + } + } + + // We timed out on the previous lock, incrementally wait + // for a longer back-off time and try again afterwards. + + return false +} + +// Unlock unlocks the write lock. +// +// It is a run-time error if lm is not locked on entry to Unlock. +func (lm *LRWMutex) Unlock() { + + isWriteLock := true + success := lm.unlock(isWriteLock) + if !success { + panic("Trying to Unlock() while no Lock() is active") + } +} + +// RUnlock releases a read lock held on lm. +// +// It is a run-time error if lm is not locked on entry to RUnlock. +func (lm *LRWMutex) RUnlock() { + + isWriteLock := false + success := lm.unlock(isWriteLock) + if !success { + panic("Trying to RUnlock() while no RLock() is active") + } +} + +func (lm *LRWMutex) unlock(isWriteLock bool) (unlocked bool) { + lm.m.Lock() + + // Try to release lock. + if isWriteLock { + if lm.isWriteLock && lm.ref == 1 { + lm.ref = 0 + lm.isWriteLock = false + unlocked = true + } + } else { + if !lm.isWriteLock { + if lm.ref > 0 { + lm.ref-- + unlocked = true + } + } + } + + lm.m.Unlock() + return unlocked +} + +// ForceUnlock will forcefully clear a write or read lock. +func (lm *LRWMutex) ForceUnlock() { + lm.m.Lock() + lm.ref = 0 + lm.isWriteLock = false + lm.m.Unlock() +} + +// DRLocker returns a sync.Locker interface that implements +// the Lock and Unlock methods by calling drw.RLock and drw.RUnlock. +func (lm *LRWMutex) DRLocker() sync.Locker { + return (*drlocker)(lm) +} + +type drlocker LRWMutex + +func (dr *drlocker) Lock() { (*LRWMutex)(dr).RLock() } +func (dr *drlocker) Unlock() { (*LRWMutex)(dr).RUnlock() } diff --git a/pkg/lsync/lrwmutex_test.go b/pkg/lsync/lrwmutex_test.go new file mode 100644 index 000000000..ac3d533b8 --- /dev/null +++ b/pkg/lsync/lrwmutex_test.go @@ -0,0 +1,338 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * 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. + */ + +// GOMAXPROCS=10 go test + +package lsync_test + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" + + "runtime" + + . "github.com/minio/minio/pkg/lsync" +) + +func testSimpleWriteLock(t *testing.T, duration time.Duration) (locked bool) { + + lrwm := NewLRWMutex(context.Background()) + + if !lrwm.GetRLock("", "object1", time.Second) { + panic("Failed to acquire read lock") + } + // fmt.Println("1st read lock acquired, waiting...") + + if !lrwm.GetRLock("", "object1", time.Second) { + panic("Failed to acquire read lock") + } + // fmt.Println("2nd read lock acquired, waiting...") + + go func() { + time.Sleep(2 * time.Second) + lrwm.RUnlock() + // fmt.Println("1st read lock released, waiting...") + }() + + go func() { + time.Sleep(3 * time.Second) + lrwm.RUnlock() + // fmt.Println("2nd read lock released, waiting...") + }() + + // fmt.Println("Trying to acquire write lock, waiting...") + locked = lrwm.GetLock("", "", duration) + if locked { + // fmt.Println("Write lock acquired, waiting...") + time.Sleep(1 * time.Second) + + lrwm.Unlock() + } else { + // fmt.Println("Write lock failed due to timeout") + } + return +} + +func TestSimpleWriteLockAcquired(t *testing.T) { + locked := testSimpleWriteLock(t, 5*time.Second) + + expected := true + if locked != expected { + t.Errorf("TestSimpleWriteLockAcquired(): \nexpected %#v\ngot %#v", expected, locked) + } +} + +func TestSimpleWriteLockTimedOut(t *testing.T) { + locked := testSimpleWriteLock(t, time.Second) + + expected := false + if locked != expected { + t.Errorf("TestSimpleWriteLockTimedOut(): \nexpected %#v\ngot %#v", expected, locked) + } +} + +func testDualWriteLock(t *testing.T, duration time.Duration) (locked bool) { + + lrwm := NewLRWMutex(context.Background()) + + // fmt.Println("Getting initial write lock") + if !lrwm.GetLock("", "", time.Second) { + panic("Failed to acquire initial write lock") + } + + go func() { + time.Sleep(2 * time.Second) + lrwm.Unlock() + // fmt.Println("Initial write lock released, waiting...") + }() + + // fmt.Println("Trying to acquire 2nd write lock, waiting...") + locked = lrwm.GetLock("", "", duration) + if locked { + // fmt.Println("2nd write lock acquired, waiting...") + time.Sleep(time.Second) + + lrwm.Unlock() + } else { + // fmt.Println("2nd write lock failed due to timeout") + } + return +} + +func TestDualWriteLockAcquired(t *testing.T) { + locked := testDualWriteLock(t, 3*time.Second) + + expected := true + if locked != expected { + t.Errorf("TestDualWriteLockAcquired(): \nexpected %#v\ngot %#v", expected, locked) + } + +} + +func TestDualWriteLockTimedOut(t *testing.T) { + locked := testDualWriteLock(t, time.Second) + + expected := false + if locked != expected { + t.Errorf("TestDualWriteLockTimedOut(): \nexpected %#v\ngot %#v", expected, locked) + } + +} + +// Test cases below are copied 1 to 1 from sync/rwmutex_test.go (adapted to use LRWMutex) + +// Borrowed from rwmutex_test.go +func parallelReader(m *LRWMutex, clocked, cunlock, cdone chan bool) { + if m.GetRLock("", "", time.Second) { + clocked <- true + <-cunlock + m.RUnlock() + cdone <- true + } +} + +// Borrowed from rwmutex_test.go +func doTestParallelReaders(numReaders, gomaxprocs int) { + runtime.GOMAXPROCS(gomaxprocs) + m := NewLRWMutex(context.Background()) + + clocked := make(chan bool) + cunlock := make(chan bool) + cdone := make(chan bool) + for i := 0; i < numReaders; i++ { + go parallelReader(m, clocked, cunlock, cdone) + } + // Wait for all parallel RLock()s to succeed. + for i := 0; i < numReaders; i++ { + <-clocked + } + for i := 0; i < numReaders; i++ { + cunlock <- true + } + // Wait for the goroutines to finish. + for i := 0; i < numReaders; i++ { + <-cdone + } +} + +// Borrowed from rwmutex_test.go +func TestParallelReaders(t *testing.T) { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) + doTestParallelReaders(1, 4) + doTestParallelReaders(3, 4) + doTestParallelReaders(4, 2) +} + +// Borrowed from rwmutex_test.go +func reader(rwm *LRWMutex, numIterations int, activity *int32, cdone chan bool) { + for i := 0; i < numIterations; i++ { + if rwm.GetRLock("", "", time.Second) { + n := atomic.AddInt32(activity, 1) + if n < 1 || n >= 10000 { + panic(fmt.Sprintf("wlock(%d)\n", n)) + } + for i := 0; i < 100; i++ { + } + atomic.AddInt32(activity, -1) + rwm.RUnlock() + } + } + cdone <- true +} + +// Borrowed from rwmutex_test.go +func writer(rwm *LRWMutex, numIterations int, activity *int32, cdone chan bool) { + for i := 0; i < numIterations; i++ { + if rwm.GetLock("", "", time.Second) { + n := atomic.AddInt32(activity, 10000) + if n != 10000 { + panic(fmt.Sprintf("wlock(%d)\n", n)) + } + for i := 0; i < 100; i++ { + } + atomic.AddInt32(activity, -10000) + rwm.Unlock() + } + } + cdone <- true +} + +// Borrowed from rwmutex_test.go +func HammerRWMutex(gomaxprocs, numReaders, numIterations int) { + runtime.GOMAXPROCS(gomaxprocs) + // Number of active readers + 10000 * number of active writers. + var activity int32 + rwm := NewLRWMutex(context.Background()) + cdone := make(chan bool) + go writer(rwm, numIterations, &activity, cdone) + var i int + for i = 0; i < numReaders/2; i++ { + go reader(rwm, numIterations, &activity, cdone) + } + go writer(rwm, numIterations, &activity, cdone) + for ; i < numReaders; i++ { + go reader(rwm, numIterations, &activity, cdone) + } + // Wait for the 2 writers and all readers to finish. + for i := 0; i < 2+numReaders; i++ { + <-cdone + } +} + +// Borrowed from rwmutex_test.go +func TestRWMutex(t *testing.T) { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) + n := 1000 + if testing.Short() { + n = 5 + } + HammerRWMutex(1, 1, n) + HammerRWMutex(1, 3, n) + HammerRWMutex(1, 10, n) + HammerRWMutex(4, 1, n) + HammerRWMutex(4, 3, n) + HammerRWMutex(4, 10, n) + HammerRWMutex(10, 1, n) + HammerRWMutex(10, 3, n) + HammerRWMutex(10, 10, n) + HammerRWMutex(10, 5, n) +} + +// Borrowed from rwmutex_test.go +func TestDRLocker(t *testing.T) { + wl := NewLRWMutex(context.Background()) + var rl sync.Locker + wlocked := make(chan bool, 1) + rlocked := make(chan bool, 1) + rl = wl.DRLocker() + n := 10 + go func() { + for i := 0; i < n; i++ { + rl.Lock() + rl.Lock() + rlocked <- true + wl.Lock() + wlocked <- true + } + }() + for i := 0; i < n; i++ { + <-rlocked + rl.Unlock() + select { + case <-wlocked: + t.Fatal("RLocker() didn't read-lock it") + default: + } + rl.Unlock() + <-wlocked + select { + case <-rlocked: + t.Fatal("RLocker() didn't respect the write lock") + default: + } + wl.Unlock() + } +} + +// Borrowed from rwmutex_test.go +func TestUnlockPanic(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatalf("unlock of unlocked RWMutex did not panic") + } + }() + mu := NewLRWMutex(context.Background()) + mu.Unlock() +} + +// Borrowed from rwmutex_test.go +func TestUnlockPanic2(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatalf("unlock of unlocked RWMutex did not panic") + } + }() + mu := NewLRWMutex(context.Background()) + mu.RLock() + mu.Unlock() +} + +// Borrowed from rwmutex_test.go +func TestRUnlockPanic(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatalf("read unlock of unlocked RWMutex did not panic") + } + }() + mu := NewLRWMutex(context.Background()) + mu.RUnlock() +} + +// Borrowed from rwmutex_test.go +func TestRUnlockPanic2(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatalf("read unlock of unlocked RWMutex did not panic") + } + }() + mu := NewLRWMutex(context.Background()) + mu.Lock() + mu.RUnlock() +} diff --git a/cmd/retry.go b/pkg/retry/retry.go similarity index 55% rename from cmd/retry.go rename to pkg/retry/retry.go index 5c7b49a05..f8423cba8 100644 --- a/cmd/retry.go +++ b/pkg/retry/retry.go @@ -1,5 +1,5 @@ /* - * MinIO Cloud Storage, (C) 2016, 2017 MinIO, Inc. + * Minio Cloud Storage, (C) 2020 MinIO, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,38 +14,15 @@ * limitations under the License. */ -package cmd +package retry import ( "context" + "math" "math/rand" - "sync" "time" ) -// lockedRandSource provides protected rand source, implements rand.Source interface. -type lockedRandSource struct { - lk sync.Mutex - src rand.Source -} - -// Int63 returns a non-negative pseudo-random 63-bit integer as an -// int64. -func (r *lockedRandSource) Int63() (n int64) { - r.lk.Lock() - n = r.src.Int63() - r.lk.Unlock() - return -} - -// Seed uses the provided seed value to initialize the generator to a -// deterministic state. -func (r *lockedRandSource) Seed(seed int64) { - r.lk.Lock() - r.src.Seed(seed) - r.lk.Unlock() -} - // MaxJitter will randomize over the full exponential backoff time const MaxJitter = 1.0 @@ -53,31 +30,48 @@ const MaxJitter = 1.0 // exponential backoff time const NoJitter = 0.0 -// Global random source for fetching random values. -var globalRandomSource = rand.New(&lockedRandSource{ - src: rand.NewSource(UTCNow().UnixNano()), -}) +// defaultTimer implements Timer interface using time.Timer +type defaultTimer struct { + timer *time.Timer +} -// newRetryTimerJitter creates a timer with exponentially increasing delays +// C returns the timers channel which receives the current time when the timer fires. +func (t *defaultTimer) C() <-chan time.Time { + return t.timer.C +} + +// Start starts the timer to fire after the given duration +// don't use this code concurrently. +func (t *defaultTimer) Start(duration time.Duration) { + if t.timer == nil { + t.timer = time.NewTimer(duration) + } else { + t.timer.Reset(duration) + } +} + +// Stop is called when the timer is not used anymore and resources may be freed. +func (t *defaultTimer) Stop() { + if t.timer != nil { + t.timer.Stop() + } +} + +// NewTimerWithJitter creates a timer with exponentially increasing delays // until the maximum retry attempts are reached. - this function is a fully // configurable version, meant for only advanced use cases. For the most part // one should use newRetryTimerSimple and newRetryTimer. -func newRetryTimerWithJitter(ctx context.Context, unit time.Duration, cap time.Duration, jitter float64) <-chan int { +func NewTimerWithJitter(ctx context.Context, unit time.Duration, cap time.Duration, jitter float64) <-chan int { attemptCh := make(chan int) // normalize jitter to the range [0, 1.0] - if jitter < NoJitter { - jitter = NoJitter - } - if jitter > MaxJitter { - jitter = MaxJitter - } + jitter = math.Max(NoJitter, math.Min(MaxJitter, jitter)) // computes the exponential backoff duration according to // https://www.awsarchitectureblog.com/2015/03/backoff.html exponentialBackoffWait := func(attempt int) time.Duration { // 1< maxAttempt { attempt = maxAttempt } @@ -87,34 +81,37 @@ func newRetryTimerWithJitter(ctx context.Context, unit time.Duration, cap time.D sleep = cap } if jitter != NoJitter { - sleep -= time.Duration(globalRandomSource.Float64() * float64(sleep) * jitter) + sleep -= time.Duration(rand.Float64() * float64(sleep) * jitter) } return sleep } go func() { - defer close(attemptCh) nextBackoff := 0 + t := &defaultTimer{} + + defer func() { + t.Stop() + }() + + defer close(attemptCh) + // Channel used to signal after the expiry of backoff wait seconds. - var timer *time.Timer for { - select { // Attempts starts. + select { case attemptCh <- nextBackoff: nextBackoff++ case <-ctx.Done(): - // Stop the routine. - return - } - timer = time.NewTimer(exponentialBackoffWait(nextBackoff)) - // wait till next backoff time or till doneCh gets a message. - select { - case <-timer.C: - case <-ctx.Done(): - // stop the timer and return. - timer.Stop() return } + t.Start(exponentialBackoffWait(nextBackoff)) + + select { + case <-ctx.Done(): + return + case <-t.C(): + } } }() @@ -124,13 +121,13 @@ func newRetryTimerWithJitter(ctx context.Context, unit time.Duration, cap time.D // Default retry constants. const ( - defaultRetryUnit = time.Second // 1 second. - defaultRetryCap = 30 * time.Second // 30 seconds. + defaultRetryUnit = 50 * time.Millisecond // 50 millisecond. + defaultRetryCap = 500 * time.Millisecond // 500 millisecond. ) -// newRetryTimerSimple creates a timer with exponentially increasing delays +// NewTimer creates a timer with exponentially increasing delays // until the maximum retry attempts are reached. - this function is a // simpler version with all default values. -func newRetryTimerSimple(ctx context.Context) <-chan int { - return newRetryTimerWithJitter(ctx, defaultRetryUnit, defaultRetryCap, MaxJitter) +func NewTimer(ctx context.Context) <-chan int { + return NewTimerWithJitter(ctx, defaultRetryUnit, defaultRetryCap, MaxJitter) } diff --git a/cmd/retry_test.go b/pkg/retry/retry_test.go similarity index 79% rename from cmd/retry_test.go rename to pkg/retry/retry_test.go index 4dc5c5534..bb67a3445 100644 --- a/cmd/retry_test.go +++ b/pkg/retry/retry_test.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2016-2020 Minio, Inc. + * Minio Cloud Storage, (C) 2020 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package cmd +package retry import ( "context" @@ -24,8 +24,8 @@ import ( // Tests for retry timer. func TestRetryTimerSimple(t *testing.T) { - rctx, cancel := context.WithCancel(context.Background()) - attemptCh := newRetryTimerSimple(rctx) + retryCtx, cancel := context.WithCancel(context.Background()) + attemptCh := NewTimer(retryCtx) i := <-attemptCh if i != 0 { cancel() @@ -45,10 +45,11 @@ func TestRetryTimerSimple(t *testing.T) { // Test retry time with no jitter. func TestRetryTimerWithNoJitter(t *testing.T) { - rctx, cancel := context.WithCancel(context.Background()) + retryCtx, cancel := context.WithCancel(context.Background()) + defer cancel() // No jitter - attemptCh := newRetryTimerWithJitter(rctx, time.Millisecond, 5*time.Millisecond, NoJitter) + attemptCh := NewTimerWithJitter(retryCtx, time.Millisecond, 5*time.Millisecond, NoJitter) i := <-attemptCh if i != 0 { cancel() @@ -57,11 +58,9 @@ func TestRetryTimerWithNoJitter(t *testing.T) { // Loop through the maximum possible attempt. for i = range attemptCh { if i == 30 { - break + cancel() } } - - cancel() _, ok := <-attemptCh if ok { t.Fatal("Attempt counter should be closed") @@ -70,10 +69,9 @@ func TestRetryTimerWithNoJitter(t *testing.T) { // Test retry time with Jitter greater than MaxJitter. func TestRetryTimerWithJitter(t *testing.T) { - rctx, cancel := context.WithCancel(context.Background()) - + retryCtx, cancel := context.WithCancel(context.Background()) // Jitter will be set back to 1.0 - attemptCh := newRetryTimerWithJitter(rctx, time.Second, 30*time.Second, 2.0) + attemptCh := NewTimerWithJitter(retryCtx, time.Second, 30*time.Second, 2.0) i := <-attemptCh if i != 0 { cancel()