2021-04-18 15:41:13 -04:00
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2016-05-06 14:57:04 -04:00
2016-08-18 19:23:42 -04:00
package cmd
2016-05-06 14:57:04 -04:00
import (
2016-10-10 04:42:32 -04:00
"bufio"
2016-06-21 15:10:18 -04:00
"bytes"
2018-03-15 16:27:16 -04:00
"context"
2016-11-11 10:18:44 -05:00
"crypto/ecdsa"
2017-09-26 14:00:07 -04:00
"crypto/hmac"
2016-11-11 10:18:44 -05:00
crand "crypto/rand"
"crypto/rsa"
2017-09-26 14:00:07 -04:00
"crypto/sha1"
2016-10-26 05:30:31 -04:00
"crypto/tls"
2016-11-11 10:18:44 -05:00
"crypto/x509"
"crypto/x509/pkix"
2017-09-26 14:00:07 -04:00
"encoding/base64"
2016-06-21 15:10:18 -04:00
"encoding/hex"
2016-11-11 10:18:44 -05:00
"encoding/pem"
2018-09-20 22:22:09 -04:00
"encoding/xml"
2016-08-15 19:13:03 -04:00
"errors"
2020-10-28 12:18:35 -04:00
"flag"
2016-06-21 15:10:18 -04:00
"fmt"
"io"
2016-05-06 14:57:04 -04:00
"io/ioutil"
2016-11-11 10:18:44 -05:00
"math/big"
2016-06-25 22:07:44 -04:00
"math/rand"
2016-10-13 12:19:04 -04:00
"net"
2016-06-21 15:10:18 -04:00
"net/http"
"net/http/httptest"
2016-06-25 22:07:44 -04:00
"net/url"
2016-05-06 14:57:04 -04:00
"os"
2021-08-08 01:43:01 -04:00
"path"
2016-09-16 16:06:49 -04:00
"reflect"
2016-06-21 15:10:18 -04:00
"sort"
2016-06-29 06:13:44 -04:00
"strconv"
2016-06-21 15:10:18 -04:00
"strings"
2016-06-29 06:13:44 -04:00
"sync"
2016-05-06 14:57:04 -04:00
"testing"
2016-06-21 15:10:18 -04:00
"time"
2016-07-02 22:05:16 -04:00
2016-10-05 15:48:07 -04:00
"github.com/fatih/color"
2021-03-29 20:00:55 -04:00
2018-04-21 22:23:54 -04:00
"github.com/gorilla/mux"
2020-07-14 12:38:05 -04:00
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/signer"
2021-06-01 17:59:40 -04:00
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/crypto"
"github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/rest"
2021-05-30 00:16:42 -04:00
"github.com/minio/pkg/bucket/policy"
2016-05-06 14:57:04 -04:00
)
2020-10-28 12:18:35 -04:00
// TestMain to set up global env.
func TestMain ( m * testing . M ) {
flag . Parse ( )
2020-11-02 10:43:11 -05:00
2020-07-12 01:19:38 -04:00
globalActiveCred = auth . Credentials {
AccessKey : auth . DefaultAccessKey ,
SecretKey : auth . DefaultSecretKey ,
}
2020-05-15 02:59:07 -04:00
// disable ENVs which interfere with tests.
for _ , env := range [ ] string {
crypto . EnvKMSAutoEncryption ,
2021-04-29 13:55:05 -04:00
config . EnvAccessKey ,
config . EnvSecretKey ,
2021-04-28 01:41:24 -04:00
config . EnvRootUser ,
config . EnvRootPassword ,
2020-05-15 02:59:07 -04:00
} {
os . Unsetenv ( env )
}
2016-12-23 10:12:19 -05:00
// Set as non-distributed.
2020-06-12 23:04:01 -04:00
globalIsDistErasure = false
2016-12-23 10:12:19 -05:00
2020-10-28 12:18:35 -04:00
if ! testing . Verbose ( ) {
// Disable printing console messages during tests.
color . Output = ioutil . Discard
logger . Disable = true
}
// Uncomment the following line to see trace logs during unit tests.
// logger.AddTarget(console.New())
2016-12-08 23:35:07 -05:00
2017-03-02 13:34:37 -05:00
// Set system resources to maximum.
setMaxResources ( )
2018-02-15 20:45:57 -05:00
2019-12-16 23:30:57 -05:00
// Initialize globalConsoleSys system
globalConsoleSys = NewConsoleLogger ( context . Background ( ) )
2020-10-16 17:49:05 -04:00
2020-11-02 10:43:11 -05:00
globalInternodeTransport = newInternodeHTTPTransport ( nil , rest . DefaultTimeout ) ( )
2019-12-04 18:32:37 -05:00
initHelp ( )
2020-06-12 23:04:01 -04:00
resetTestGlobals ( )
2020-10-28 12:18:35 -04:00
2020-12-12 19:10:07 -05:00
os . Setenv ( "MINIO_CI_CD" , "ci" )
2020-10-28 12:18:35 -04:00
os . Exit ( m . Run ( ) )
2016-07-07 22:50:44 -04:00
}
2020-10-28 12:18:35 -04:00
// concurrency level for certain parallel tests.
2017-10-10 05:14:42 -04:00
const testConcurrencyLevel = 10
2017-10-09 19:41:35 -04:00
2021-11-16 12:28:29 -05:00
//
// Excerpts from @lsegal - https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258
//
// User-Agent:
//
// This is ignored from signing because signing this causes problems with generating pre-signed URLs
// (that are executed by other agents) or when customers pass requests through proxies, which may
// modify the user-agent.
//
// Authorization:
//
// Is skipped for obvious reasons
//
2017-10-09 19:41:35 -04:00
var ignoredHeaders = map [ string ] bool {
2020-04-14 02:21:01 -04:00
"Authorization" : true ,
"User-Agent" : true ,
2017-10-09 19:41:35 -04:00
}
// Headers to ignore in streaming v4
var ignoredStreamingHeaders = map [ string ] bool {
"Authorization" : true ,
"Content-Type" : true ,
"Content-Md5" : true ,
"User-Agent" : true ,
}
// calculateSignedChunkLength - calculates the length of chunk metadata
func calculateSignedChunkLength ( chunkDataSize int64 ) int64 {
return int64 ( len ( fmt . Sprintf ( "%x" , chunkDataSize ) ) ) +
17 + // ";chunk-signature="
64 + // e.g. "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2"
2 + // CRLF
chunkDataSize +
2 // CRLF
}
2018-11-14 20:36:41 -05:00
func mustGetPutObjReader ( t TestErrHandler , data io . Reader , size int64 , md5hex , sha256hex string ) * PutObjReader {
pkg/etag: add new package for S3 ETag handling (#11577)
This commit adds a new package `etag` for dealing
with S3 ETags.
Even though ETag is often viewed as MD5 checksum of
an object, handling S3 ETags correctly is a surprisingly
complex task. While it is true that the ETag corresponds
to the MD5 for the most basic S3 API operations, there are
many exceptions in case of multipart uploads or encryption.
In worse, some S3 clients expect very specific behavior when
it comes to ETags. For example, some clients expect that the
ETag is a double-quoted string and fail otherwise.
Non-AWS compliant ETag handling has been a source of many bugs
in the past.
Therefore, this commit adds a dedicated `etag` package that provides
functionality for parsing, generating and converting S3 ETags.
Further, this commit removes the ETag computation from the `hash`
package. Instead, the `hash` package (i.e. `hash.Reader`) should
focus only on computing and verifying the content-sha256.
One core feature of this commit is to provide a mechanism to
communicate a computed ETag from a low-level `io.Reader` to
a high-level `io.Reader`.
This problem occurs when an S3 server receives a request and
has to compute the ETag of the content. However, the server
may also wrap the initial body with several other `io.Reader`,
e.g. when encrypting or compressing the content:
```
reader := Encrypt(Compress(ETag(content)))
```
In such a case, the ETag should be accessible by the high-level
`io.Reader`.
The `etag` provides a mechanism to wrap `io.Reader` implementations
such that the `ETag` can be accessed by a type-check.
This technique is applied to the PUT, COPY and Upload handlers.
2021-02-23 15:31:53 -05:00
hr , err := hash . NewReader ( data , size , md5hex , sha256hex , size )
2017-10-22 01:30:34 -04:00
if err != nil {
t . Fatal ( err )
}
2021-02-10 11:52:50 -05:00
return NewPutObjReader ( hr )
2017-10-22 01:30:34 -04:00
}
2017-10-09 19:41:35 -04:00
// calculateSignedChunkLength - calculates the length of the overall stream (data + metadata)
func calculateStreamContentLength ( dataLen , chunkSize int64 ) int64 {
if dataLen <= 0 {
return 0
}
2020-08-24 15:11:20 -04:00
chunksCount := dataLen / chunkSize
remainingBytes := dataLen % chunkSize
2017-10-09 19:41:35 -04:00
var streamLen int64
streamLen += chunksCount * calculateSignedChunkLength ( chunkSize )
if remainingBytes > 0 {
streamLen += calculateSignedChunkLength ( remainingBytes )
}
streamLen += calculateSignedChunkLength ( 0 )
return streamLen
}
2016-08-30 22:22:27 -04:00
func prepareFS ( ) ( ObjectLayer , string , error ) {
2017-01-16 20:05:00 -05:00
nDisks := 1
fsDirs , err := getRandomDisks ( nDisks )
2016-08-30 22:22:27 -04:00
if err != nil {
return nil , "" , err
}
2018-02-20 15:21:12 -05:00
obj , err := NewFSObjectLayer ( fsDirs [ 0 ] )
2017-01-16 20:05:00 -05:00
if err != nil {
return nil , "" , err
}
2017-02-28 21:05:52 -05:00
return obj , fsDirs [ 0 ] , nil
2016-08-30 22:22:27 -04:00
}
2020-06-12 23:04:01 -04:00
func prepareErasureSets32 ( ctx context . Context ) ( ObjectLayer , [ ] string , error ) {
return prepareErasure ( ctx , 32 )
2018-02-15 20:45:57 -05:00
}
2020-06-12 23:04:01 -04:00
func prepareErasure ( ctx context . Context , nDisks int ) ( ObjectLayer , [ ] string , error ) {
2016-08-30 22:22:27 -04:00
fsDirs , err := getRandomDisks ( nDisks )
if err != nil {
return nil , nil , err
}
2021-01-26 23:47:42 -05:00
obj , _ , err := initObjectLayer ( ctx , mustGetPoolEndpoints ( fsDirs ... ) )
2016-08-30 22:22:27 -04:00
if err != nil {
removeRoots ( fsDirs )
return nil , nil , err
}
return obj , fsDirs , nil
}
2020-06-12 23:04:01 -04:00
func prepareErasure16 ( ctx context . Context ) ( ObjectLayer , [ ] string , error ) {
return prepareErasure ( ctx , 16 )
2017-12-28 12:32:48 -05:00
}
2017-01-16 20:05:00 -05:00
// Initialize FS objects.
func initFSObjects ( disk string , t * testing . T ) ( obj ObjectLayer ) {
var err error
2018-02-20 15:21:12 -05:00
obj , err = NewFSObjectLayer ( disk )
2017-01-16 20:05:00 -05:00
if err != nil {
t . Fatal ( err )
}
2021-10-04 13:52:28 -04:00
2018-08-15 00:41:47 -04:00
newTestConfig ( globalMinioDefaultRegion , obj )
2021-10-04 13:52:28 -04:00
newAllSubsystems ( )
2017-01-16 20:05:00 -05:00
return obj
}
2020-06-12 23:04:01 -04:00
// TestErrHandler - Go testing.T satisfy this interface.
2016-06-21 15:10:18 -04:00
// This makes it easy to run the TestServer from any of the tests.
2020-06-12 23:04:01 -04:00
// Using this interface, functionalities to be used in tests can be
// made generalized, and can be integrated in benchmarks/unit tests/go check suite tests.
2016-06-21 15:10:18 -04:00
type TestErrHandler interface {
2021-03-29 20:00:55 -04:00
testing . TB
2016-06-21 15:10:18 -04:00
}
2016-05-06 14:57:04 -04:00
const (
2016-11-01 13:21:16 -04:00
// FSTestStr is the string which is used as notation for Single node ObjectLayer in the unit tests.
FSTestStr string = "FS"
2017-01-16 20:05:00 -05:00
2020-06-12 23:04:01 -04:00
// ErasureTestStr is the string which is used as notation for Erasure ObjectLayer in the unit tests.
ErasureTestStr string = "Erasure"
2018-02-15 20:45:57 -05:00
2020-06-12 23:04:01 -04:00
// ErasureSetsTestStr is the string which is used as notation for Erasure sets object layer in the unit tests.
ErasureSetsTestStr string = "ErasureSet"
2016-05-06 14:57:04 -04:00
)
2016-06-25 22:07:44 -04:00
const letterBytes = "abcdefghijklmnopqrstuvwxyz01234569"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1 << letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
2016-06-29 06:13:44 -04:00
// Random number state.
// We generate random temporary file names so that there's a good
// chance the file doesn't exist yet.
2022-01-06 16:08:21 -05:00
var (
randN uint32
randmu sync . Mutex
)
2016-06-29 06:13:44 -04:00
2016-12-16 01:25:05 -05:00
// Temp files created in default Tmp dir
var globalTestTmpDir = os . TempDir ( )
2016-07-02 04:59:28 -04:00
// reseed - returns a new seed every time the function is called.
2016-06-29 06:13:44 -04:00
func reseed ( ) uint32 {
return uint32 ( time . Now ( ) . UnixNano ( ) + int64 ( os . Getpid ( ) ) )
}
2016-07-02 04:59:28 -04:00
// nextSuffix - provides a new unique suffix every time the function is called.
2016-06-29 06:13:44 -04:00
func nextSuffix ( ) string {
randmu . Lock ( )
r := randN
// Initial seed required, generate one.
if r == 0 {
r = reseed ( )
}
// constants from Numerical Recipes
r = r * 1664525 + 1013904223
randN = r
randmu . Unlock ( )
return strconv . Itoa ( int ( 1e9 + r % 1e9 ) ) [ 1 : ]
}
2016-09-16 16:06:49 -04:00
// isSameType - compares two object types via reflect.TypeOf
func isSameType ( obj1 , obj2 interface { } ) bool {
return reflect . TypeOf ( obj1 ) == reflect . TypeOf ( obj2 )
}
2019-04-09 14:39:42 -04:00
// TestServer encapsulates an instantiation of a MinIO instance with a temporary backend.
2016-06-21 15:10:18 -04:00
// Example usage:
2020-06-12 23:04:01 -04:00
// s := StartTestServer(t,"Erasure")
2016-06-21 15:10:18 -04:00
// defer s.Stop()
type TestServer struct {
2021-11-04 11:16:30 -04:00
Root string
Disks EndpointServerPools
AccessKey string
SecretKey string
Server * httptest . Server
Obj ObjectLayer
cancel context . CancelFunc
rawDiskPaths [ ] string
2016-06-21 15:10:18 -04:00
}
2020-06-12 23:04:01 -04:00
// UnstartedTestServer - Configures a temp FS/Erasure backend,
2017-02-28 21:05:52 -05:00
// initializes the endpoints and configures the test server.
// The server should be started using the Start() method.
2016-10-26 05:30:31 -04:00
func UnstartedTestServer ( t TestErrHandler , instanceType string ) TestServer {
2020-04-14 20:52:38 -04:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
2016-06-21 15:10:18 -04:00
// create an instance of TestServer.
2020-04-14 20:52:38 -04:00
testServer := TestServer { cancel : cancel }
2020-06-12 23:04:01 -04:00
// return FS/Erasure object layer and temp backend.
2020-04-14 20:52:38 -04:00
objLayer , disks , err := prepareTestBackend ( ctx , instanceType )
2016-06-21 15:10:18 -04:00
if err != nil {
2017-02-28 21:05:52 -05:00
t . Fatal ( err )
2016-06-21 15:10:18 -04:00
}
2018-08-15 00:41:47 -04:00
2021-11-04 11:16:30 -04:00
// set new server configuration.
2018-08-15 00:41:47 -04:00
if err = newTestConfig ( globalMinioDefaultRegion , objLayer ) ; err != nil {
2016-07-02 22:05:16 -04:00
t . Fatalf ( "%s" , err )
2016-06-21 15:10:18 -04:00
}
2016-07-26 03:01:35 -04:00
2021-11-04 11:16:30 -04:00
return initTestServerWithBackend ( ctx , t , testServer , objLayer , disks )
}
// initializes a test server with the given object layer and disks.
func initTestServerWithBackend ( ctx context . Context , t TestErrHandler , testServer TestServer , objLayer ObjectLayer , disks [ ] string ) TestServer {
2016-08-30 22:22:27 -04:00
// Test Server needs to start before formatting of disks.
2016-07-26 03:01:35 -04:00
// Get credential.
2019-10-23 01:59:13 -04:00
credentials := globalActiveCred
2016-07-26 03:01:35 -04:00
2017-02-28 21:05:52 -05:00
testServer . Obj = objLayer
2021-11-04 11:16:30 -04:00
testServer . rawDiskPaths = disks
2021-01-26 23:47:42 -05:00
testServer . Disks = mustGetPoolEndpoints ( disks ... )
2016-12-26 13:21:23 -05:00
testServer . AccessKey = credentials . AccessKey
testServer . SecretKey = credentials . SecretKey
2016-07-02 22:05:16 -04:00
2020-05-25 03:17:52 -04:00
httpHandler , err := configureServerHandler ( testServer . Disks )
2016-10-13 02:13:24 -04:00
if err != nil {
t . Fatalf ( "Failed to configure one of the RPC services <ERROR> %s" , err )
}
2016-10-13 12:19:04 -04:00
// Run TestServer.
2021-11-01 11:04:03 -04:00
testServer . Server = httptest . NewUnstartedServer ( setCriticalErrorHandler ( corsHandler ( httpHandler ) ) )
2016-10-13 12:19:04 -04:00
2016-10-10 02:03:10 -04:00
globalObjLayerMutex . Lock ( )
2016-08-30 22:22:27 -04:00
globalObjectAPI = objLayer
2016-10-10 02:03:10 -04:00
globalObjLayerMutex . Unlock ( )
2016-10-13 12:19:04 -04:00
// initialize peer rpc
2017-04-11 18:44:27 -04:00
host , port := mustSplitHostPort ( testServer . Server . Listener . Addr ( ) . String ( ) )
2016-10-27 06:30:52 -04:00
globalMinioHost = host
globalMinioPort = port
2017-04-11 18:44:27 -04:00
globalMinioAddr = getEndpointsLocalAddr ( testServer . Disks )
2018-07-09 21:50:31 -04:00
2020-05-15 02:59:07 -04:00
newAllSubsystems ( )
2018-04-24 18:53:30 -04:00
2021-11-09 12:25:13 -05:00
globalEtcdClient = nil
2021-11-17 16:42:08 -05:00
initConfigSubsystem ( ctx , objLayer )
2020-02-05 04:42:34 -05:00
2021-11-29 17:38:57 -05:00
globalIAMSys . Init ( ctx , objLayer , globalEtcdClient , globalNotificationSys , 2 * time . Second )
2021-05-09 11:14:19 -04:00
2016-07-02 22:05:16 -04:00
return testServer
2016-10-26 05:30:31 -04:00
}
2016-11-11 10:18:44 -05:00
// testServerCertPEM and testServerKeyPEM are generated by
// https://golang.org/src/crypto/tls/generate_cert.go
// $ go run generate_cert.go -ca --host 127.0.0.1
// The generated certificate contains IP SAN, that way we don't need
// to enable InsecureSkipVerify in TLS config
// Starts the test server and returns the TestServer with TLS configured instance.
func StartTestTLSServer ( t TestErrHandler , instanceType string , cert , key [ ] byte ) TestServer {
2016-10-26 05:30:31 -04:00
// Fetch TLS key and pem files from test-data/ directory.
// dir, _ := os.Getwd()
// testDataDir := filepath.Join(filepath.Dir(dir), "test-data")
//
// pemFile := filepath.Join(testDataDir, "server.pem")
// keyFile := filepath.Join(testDataDir, "server.key")
2016-11-11 10:18:44 -05:00
cer , err := tls . X509KeyPair ( cert , key )
2016-10-26 05:30:31 -04:00
if err != nil {
t . Fatalf ( "Failed to load certificate: %v" , err )
}
config := & tls . Config { Certificates : [ ] tls . Certificate { cer } }
testServer := UnstartedTestServer ( t , instanceType )
testServer . Server . TLS = config
testServer . Server . StartTLS ( )
return testServer
}
// Starts the test server and returns the TestServer instance.
func StartTestServer ( t TestErrHandler , instanceType string ) TestServer {
// create an instance of TestServer.
testServer := UnstartedTestServer ( t , instanceType )
testServer . Server . Start ( )
return testServer
2016-07-02 22:05:16 -04:00
}
2016-12-13 14:51:48 -05:00
// Sets the global config path to empty string.
func resetGlobalConfigPath ( ) {
2019-01-02 13:05:16 -05:00
globalConfigDir = & ConfigDir { path : "" }
2016-12-13 14:51:48 -05:00
}
// sets globalObjectAPI to `nil`.
func resetGlobalObjectAPI ( ) {
globalObjLayerMutex . Lock ( )
globalObjectAPI = nil
globalObjLayerMutex . Unlock ( )
}
// reset the value of the Global server config.
// set it to `nil`.
func resetGlobalConfig ( ) {
// hold the mutex lock before a new config is assigned.
2017-11-29 16:12:47 -05:00
globalServerConfigMu . Lock ( )
2016-12-13 14:51:48 -05:00
// Save the loaded config globally.
2017-11-29 16:12:47 -05:00
globalServerConfig = nil
globalServerConfigMu . Unlock ( )
2016-12-13 14:51:48 -05:00
}
2017-01-23 03:32:55 -05:00
func resetGlobalEndpoints ( ) {
2020-12-01 16:50:33 -05:00
globalEndpoints = EndpointServerPools { }
2017-01-23 03:32:55 -05:00
}
2020-06-12 23:04:01 -04:00
func resetGlobalIsErasure ( ) {
globalIsErasure = false
2017-01-23 03:32:55 -05:00
}
2018-01-22 17:54:55 -05:00
// reset global heal state
func resetGlobalHealState ( ) {
2020-01-15 21:30:32 -05:00
// Init global heal state
2019-06-09 01:14:07 -04:00
if globalAllHealState == nil {
2020-12-13 14:57:08 -05:00
globalAllHealState = newHealState ( false )
2020-01-15 21:30:32 -05:00
} else {
globalAllHealState . Lock ( )
for _ , v := range globalAllHealState . healSeqMap {
if ! v . hasEnded ( ) {
v . stop ( )
}
}
globalAllHealState . Unlock ( )
2019-06-09 01:14:07 -04:00
}
2020-01-15 21:30:32 -05:00
// Init background heal state
if globalBackgroundHealState == nil {
2020-12-13 14:57:08 -05:00
globalBackgroundHealState = newHealState ( false )
2020-01-15 21:30:32 -05:00
} else {
globalBackgroundHealState . Lock ( )
for _ , v := range globalBackgroundHealState . healSeqMap {
if ! v . hasEnded ( ) {
v . stop ( )
}
2018-01-22 17:54:55 -05:00
}
2020-01-15 21:30:32 -05:00
globalBackgroundHealState . Unlock ( )
2018-01-22 17:54:55 -05:00
}
}
2018-10-17 20:25:50 -04:00
// sets globalIAMSys to `nil`.
func resetGlobalIAMSys ( ) {
globalIAMSys = nil
}
2016-12-13 14:51:48 -05:00
// Resets all the globals used modified in tests.
// Resetting ensures that the changes made to globals by one test doesn't affect others.
func resetTestGlobals ( ) {
// set globalObjectAPI to `nil`.
resetGlobalObjectAPI ( )
// Reset config path set.
resetGlobalConfigPath ( )
// Reset Global server config.
resetGlobalConfig ( )
2017-01-23 03:32:55 -05:00
// Reset global endpoints.
resetGlobalEndpoints ( )
2020-06-12 23:04:01 -04:00
// Reset global isErasure flag.
resetGlobalIsErasure ( )
2018-01-22 17:54:55 -05:00
// Reset global heal state
resetGlobalHealState ( )
2018-10-17 20:25:50 -04:00
// Reset globalIAMSys to `nil`
resetGlobalIAMSys ( )
2016-12-13 14:51:48 -05:00
}
2016-07-02 22:05:16 -04:00
// Configure the server for the test run.
2018-08-15 00:41:47 -04:00
func newTestConfig ( bucketLocation string , obj ObjectLayer ) ( err error ) {
2016-07-26 03:01:35 -04:00
// Initialize server config.
2018-10-09 17:00:01 -04:00
if err = newSrvConfig ( obj ) ; err != nil {
2018-08-15 00:41:47 -04:00
return err
2016-06-21 15:10:18 -04:00
}
2016-07-26 03:01:35 -04:00
// Set a default region.
2019-10-23 01:59:13 -04:00
config . SetRegion ( globalServerConfig , bucketLocation )
2016-07-26 03:01:35 -04:00
// Save config.
2019-11-21 07:24:51 -05:00
return saveServerConfig ( context . Background ( ) , obj , globalServerConfig )
2016-06-21 15:10:18 -04:00
}
// Deleting the temporary backend and stopping the server.
func ( testServer TestServer ) Stop ( ) {
2020-04-14 20:52:38 -04:00
testServer . cancel ( )
testServer . Server . Close ( )
2020-09-10 12:18:19 -04:00
testServer . Obj . Shutdown ( context . Background ( ) )
2017-08-12 22:25:43 -04:00
os . RemoveAll ( testServer . Root )
2019-11-19 20:42:27 -05:00
for _ , ep := range testServer . Disks {
for _ , disk := range ep . Endpoints {
os . RemoveAll ( disk . Path )
}
2016-06-21 15:10:18 -04:00
}
}
2016-10-10 04:42:32 -04:00
// Truncate request to simulate unexpected EOF for a request signed using streaming signature v4.
func truncateChunkByHalfSigv4 ( req * http . Request ) ( * http . Request , error ) {
bufReader := bufio . NewReader ( req . Body )
hexChunkSize , chunkSignature , err := readChunkLine ( bufReader )
if err != nil {
return nil , err
}
newChunkHdr := [ ] byte ( fmt . Sprintf ( "%s" + s3ChunkSignatureStr + "%s\r\n" ,
hexChunkSize , chunkSignature ) )
newChunk , err := ioutil . ReadAll ( bufReader )
if err != nil {
return nil , err
}
newReq := req
newReq . Body = ioutil . NopCloser (
bytes . NewReader ( bytes . Join ( [ ] [ ] byte { newChunkHdr , newChunk [ : len ( newChunk ) / 2 ] } ,
[ ] byte ( "" ) ) ) ,
)
return newReq , nil
}
// Malform data given a request signed using streaming signature V4.
func malformDataSigV4 ( req * http . Request , newByte byte ) ( * http . Request , error ) {
bufReader := bufio . NewReader ( req . Body )
hexChunkSize , chunkSignature , err := readChunkLine ( bufReader )
if err != nil {
return nil , err
}
newChunkHdr := [ ] byte ( fmt . Sprintf ( "%s" + s3ChunkSignatureStr + "%s\r\n" ,
hexChunkSize , chunkSignature ) )
newChunk , err := ioutil . ReadAll ( bufReader )
if err != nil {
return nil , err
}
newChunk [ 0 ] = newByte
newReq := req
newReq . Body = ioutil . NopCloser (
bytes . NewReader ( bytes . Join ( [ ] [ ] byte { newChunkHdr , newChunk } ,
[ ] byte ( "" ) ) ) ,
)
return newReq , nil
}
// Malform chunk size given a request signed using streaming signatureV4.
func malformChunkSizeSigV4 ( req * http . Request , badSize int64 ) ( * http . Request , error ) {
bufReader := bufio . NewReader ( req . Body )
_ , chunkSignature , err := readChunkLine ( bufReader )
if err != nil {
return nil , err
}
n := badSize
newHexChunkSize := [ ] byte ( fmt . Sprintf ( "%x" , n ) )
newChunkHdr := [ ] byte ( fmt . Sprintf ( "%s" + s3ChunkSignatureStr + "%s\r\n" ,
newHexChunkSize , chunkSignature ) )
newChunk , err := ioutil . ReadAll ( bufReader )
if err != nil {
return nil , err
}
newReq := req
newReq . Body = ioutil . NopCloser (
bytes . NewReader ( bytes . Join ( [ ] [ ] byte { newChunkHdr , newChunk } ,
[ ] byte ( "" ) ) ) ,
)
return newReq , nil
}
2016-09-04 16:37:14 -04:00
// Sign given request using Signature V4.
2016-10-12 02:46:51 -04:00
func signStreamingRequest ( req * http . Request , accessKey , secretKey string , currTime time . Time ) ( string , error ) {
2016-09-04 16:37:14 -04:00
// Get hashed payload.
hashedPayload := req . Header . Get ( "x-amz-content-sha256" )
if hashedPayload == "" {
2016-11-15 21:14:23 -05:00
return "" , fmt . Errorf ( "Invalid hashed payload" )
2016-09-04 16:37:14 -04:00
}
// Set x-amz-date.
req . Header . Set ( "x-amz-date" , currTime . Format ( iso8601Format ) )
// Get header map.
headerMap := make ( map [ string ] [ ] string )
for k , vv := range req . Header {
// If request header key is not in ignored headers, then add it.
if _ , ok := ignoredStreamingHeaders [ http . CanonicalHeaderKey ( k ) ] ; ! ok {
headerMap [ strings . ToLower ( k ) ] = vv
}
}
// Get header keys.
headers := [ ] string { "host" }
for k := range headerMap {
headers = append ( headers , k )
}
sort . Strings ( headers )
// Get canonical headers.
var buf bytes . Buffer
for _ , k := range headers {
buf . WriteString ( k )
buf . WriteByte ( ':' )
switch {
case k == "host" :
buf . WriteString ( req . URL . Host )
fallthrough
default :
for idx , v := range headerMap [ k ] {
if idx > 0 {
buf . WriteByte ( ',' )
}
buf . WriteString ( v )
}
buf . WriteByte ( '\n' )
}
}
canonicalHeaders := buf . String ( )
// Get signed headers.
signedHeaders := strings . Join ( headers , ";" )
// Get canonical query string.
2021-11-16 12:28:29 -05:00
req . URL . RawQuery = strings . ReplaceAll ( req . URL . Query ( ) . Encode ( ) , "+" , "%20" )
2016-09-04 16:37:14 -04:00
// Get canonical URI.
2018-06-05 13:48:51 -04:00
canonicalURI := s3utils . EncodePath ( req . URL . Path )
2016-09-04 16:37:14 -04:00
// Get canonical request.
// canonicalRequest =
// <HTTPMethod>\n
// <CanonicalURI>\n
// <CanonicalQueryString>\n
// <CanonicalHeaders>\n
// <SignedHeaders>\n
// <HashedPayload>
//
canonicalRequest := strings . Join ( [ ] string {
req . Method ,
canonicalURI ,
req . URL . RawQuery ,
canonicalHeaders ,
signedHeaders ,
hashedPayload ,
} , "\n" )
// Get scope.
scope := strings . Join ( [ ] string {
currTime . Format ( yyyymmdd ) ,
2017-01-18 15:24:34 -05:00
globalMinioDefaultRegion ,
2019-02-27 20:46:55 -05:00
string ( serviceS3 ) ,
2016-09-04 16:37:14 -04:00
"aws4_request" ,
2019-08-06 15:08:58 -04:00
} , SlashSeparator )
2016-09-04 16:37:14 -04:00
stringToSign := "AWS4-HMAC-SHA256" + "\n" + currTime . Format ( iso8601Format ) + "\n"
2021-11-16 12:28:29 -05:00
stringToSign += scope + "\n"
stringToSign += getSHA256Hash ( [ ] byte ( canonicalRequest ) )
2016-09-04 16:37:14 -04:00
date := sumHMAC ( [ ] byte ( "AWS4" + secretKey ) , [ ] byte ( currTime . Format ( yyyymmdd ) ) )
2017-01-18 15:24:34 -05:00
region := sumHMAC ( date , [ ] byte ( globalMinioDefaultRegion ) )
2019-02-27 20:46:55 -05:00
service := sumHMAC ( region , [ ] byte ( string ( serviceS3 ) ) )
2016-09-04 16:37:14 -04:00
signingKey := sumHMAC ( service , [ ] byte ( "aws4_request" ) )
signature := hex . EncodeToString ( sumHMAC ( signingKey , [ ] byte ( stringToSign ) ) )
// final Authorization header
parts := [ ] string {
2019-08-06 15:08:58 -04:00
"AWS4-HMAC-SHA256" + " Credential=" + accessKey + SlashSeparator + scope ,
2016-09-04 16:37:14 -04:00
"SignedHeaders=" + signedHeaders ,
"Signature=" + signature ,
}
auth := strings . Join ( parts , ", " )
req . Header . Set ( "Authorization" , auth )
return signature , nil
}
// Returns new HTTP request object.
func newTestStreamingRequest ( method , urlStr string , dataLength , chunkSize int64 , body io . ReadSeeker ) ( * http . Request , error ) {
if method == "" {
2020-07-20 15:52:49 -04:00
method = http . MethodPost
2016-09-04 16:37:14 -04:00
}
req , err := http . NewRequest ( method , urlStr , nil )
if err != nil {
return nil , err
}
if body == nil {
// this is added to avoid panic during ioutil.ReadAll(req.Body).
// th stack trace can be found here https://github.com/minio/minio/pull/2074 .
// This is very similar to https://github.com/golang/go/issues/7527.
req . Body = ioutil . NopCloser ( bytes . NewReader ( [ ] byte ( "" ) ) )
}
contentLength := calculateStreamContentLength ( dataLength , chunkSize )
req . Header . Set ( "x-amz-content-sha256" , "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" )
req . Header . Set ( "content-encoding" , "aws-chunked" )
req . Header . Set ( "x-amz-decoded-content-length" , strconv . FormatInt ( dataLength , 10 ) )
req . Header . Set ( "content-length" , strconv . FormatInt ( contentLength , 10 ) )
// Seek back to beginning.
body . Seek ( 0 , 0 )
2016-09-16 05:45:42 -04:00
2016-09-04 16:37:14 -04:00
// Add body
req . Body = ioutil . NopCloser ( body )
req . ContentLength = contentLength
return req , nil
}
2016-10-12 02:46:51 -04:00
func assembleStreamingChunks ( req * http . Request , body io . ReadSeeker , chunkSize int64 ,
2022-01-02 12:15:06 -05:00
secretKey , signature string , currTime time . Time ) ( * http . Request , error ,
) {
2021-11-25 16:06:25 -05:00
regionStr := globalSite . Region
2016-09-04 16:37:14 -04:00
var stream [ ] byte
var buffer [ ] byte
body . Seek ( 0 , 0 )
for {
buffer = make ( [ ] byte , chunkSize )
n , err := body . Read ( buffer )
if err != nil && err != io . EOF {
return nil , err
}
// Get scope.
scope := strings . Join ( [ ] string {
currTime . Format ( yyyymmdd ) ,
2016-09-16 05:45:42 -04:00
regionStr ,
2019-02-27 20:46:55 -05:00
string ( serviceS3 ) ,
2016-09-04 16:37:14 -04:00
"aws4_request" ,
2019-08-06 15:08:58 -04:00
} , SlashSeparator )
2016-09-04 16:37:14 -04:00
stringToSign := "AWS4-HMAC-SHA256-PAYLOAD" + "\n"
stringToSign = stringToSign + currTime . Format ( iso8601Format ) + "\n"
stringToSign = stringToSign + scope + "\n"
stringToSign = stringToSign + signature + "\n"
stringToSign = stringToSign + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + "\n" // hex(sum256(""))
2021-11-16 12:28:29 -05:00
stringToSign += getSHA256Hash ( buffer [ : n ] )
2016-09-04 16:37:14 -04:00
date := sumHMAC ( [ ] byte ( "AWS4" + secretKey ) , [ ] byte ( currTime . Format ( yyyymmdd ) ) )
2016-09-16 05:45:42 -04:00
region := sumHMAC ( date , [ ] byte ( regionStr ) )
2019-02-27 20:46:55 -05:00
service := sumHMAC ( region , [ ] byte ( serviceS3 ) )
2016-09-04 16:37:14 -04:00
signingKey := sumHMAC ( service , [ ] byte ( "aws4_request" ) )
signature = hex . EncodeToString ( sumHMAC ( signingKey , [ ] byte ( stringToSign ) ) )
stream = append ( stream , [ ] byte ( fmt . Sprintf ( "%x" , n ) + ";chunk-signature=" + signature + "\r\n" ) ... )
stream = append ( stream , buffer [ : n ] ... )
stream = append ( stream , [ ] byte ( "\r\n" ) ... )
if n <= 0 {
break
}
}
req . Body = ioutil . NopCloser ( bytes . NewReader ( stream ) )
return req , nil
}
2016-10-12 02:46:51 -04:00
func newTestStreamingSignedBadChunkDateRequest ( method , urlStr string , contentLength , chunkSize int64 , body io . ReadSeeker , accessKey , secretKey string ) ( * http . Request , error ) {
req , err := newTestStreamingRequest ( method , urlStr , contentLength , chunkSize , body )
if err != nil {
return nil , err
}
2017-03-18 14:28:41 -04:00
currTime := UTCNow ( )
2016-10-12 02:46:51 -04:00
signature , err := signStreamingRequest ( req , accessKey , secretKey , currTime )
if err != nil {
return nil , err
}
// skew the time between the chunk signature calculation and seed signature.
currTime = currTime . Add ( 1 * time . Second )
req , err = assembleStreamingChunks ( req , body , chunkSize , secretKey , signature , currTime )
2016-10-31 02:32:46 -04:00
return req , err
2016-10-12 02:46:51 -04:00
}
2017-03-27 20:02:04 -04:00
func newTestStreamingSignedCustomEncodingRequest ( method , urlStr string , contentLength , chunkSize int64 , body io . ReadSeeker , accessKey , secretKey , contentEncoding string ) ( * http . Request , error ) {
req , err := newTestStreamingRequest ( method , urlStr , contentLength , chunkSize , body )
if err != nil {
return nil , err
}
// Set custom encoding.
req . Header . Set ( "content-encoding" , contentEncoding )
currTime := UTCNow ( )
signature , err := signStreamingRequest ( req , accessKey , secretKey , currTime )
if err != nil {
return nil , err
}
req , err = assembleStreamingChunks ( req , body , chunkSize , secretKey , signature , currTime )
return req , err
}
2016-10-12 02:46:51 -04:00
// Returns new HTTP request object signed with streaming signature v4.
func newTestStreamingSignedRequest ( method , urlStr string , contentLength , chunkSize int64 , body io . ReadSeeker , accessKey , secretKey string ) ( * http . Request , error ) {
req , err := newTestStreamingRequest ( method , urlStr , contentLength , chunkSize , body )
if err != nil {
return nil , err
}
2017-03-18 14:28:41 -04:00
currTime := UTCNow ( )
2016-10-12 02:46:51 -04:00
signature , err := signStreamingRequest ( req , accessKey , secretKey , currTime )
if err != nil {
return nil , err
}
req , err = assembleStreamingChunks ( req , body , chunkSize , secretKey , signature , currTime )
2016-10-31 02:32:46 -04:00
return req , err
2016-10-12 02:46:51 -04:00
}
2016-11-11 00:57:15 -05:00
// preSignV4 presign the request, in accordance with
// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html.
func preSignV4 ( req * http . Request , accessKeyID , secretAccessKey string , expires int64 ) error {
// Presign is not needed for anonymous credentials.
if accessKeyID == "" || secretAccessKey == "" {
return errors . New ( "Presign cannot be generated without access and secret keys" )
}
2021-11-25 16:06:25 -05:00
region := globalSite . Region
2017-03-18 14:28:41 -04:00
date := UTCNow ( )
2017-02-06 16:09:09 -05:00
scope := getScope ( date , region )
credential := fmt . Sprintf ( "%s/%s" , accessKeyID , scope )
2016-11-11 00:57:15 -05:00
// Set URL query.
query := req . URL . Query ( )
query . Set ( "X-Amz-Algorithm" , signV4Algorithm )
query . Set ( "X-Amz-Date" , date . Format ( iso8601Format ) )
query . Set ( "X-Amz-Expires" , strconv . FormatInt ( expires , 10 ) )
query . Set ( "X-Amz-SignedHeaders" , "host" )
query . Set ( "X-Amz-Credential" , credential )
query . Set ( "X-Amz-Content-Sha256" , unsignedPayload )
2017-04-05 20:00:24 -04:00
// "host" is the only header required to be signed for Presigned URLs.
extractedSignedHeaders := make ( http . Header )
extractedSignedHeaders . Set ( "host" , req . Host )
2016-11-11 00:57:15 -05:00
2021-11-16 12:28:29 -05:00
queryStr := strings . ReplaceAll ( query . Encode ( ) , "+" , "%20" )
2017-04-05 20:00:24 -04:00
canonicalRequest := getCanonicalRequest ( extractedSignedHeaders , unsignedPayload , queryStr , req . URL . Path , req . Method )
2017-02-06 16:09:09 -05:00
stringToSign := getStringToSign ( canonicalRequest , date , scope )
2019-02-27 20:46:55 -05:00
signingKey := getSigningKey ( secretAccessKey , date , region , serviceS3 )
2016-11-11 00:57:15 -05:00
signature := getSignature ( signingKey , stringToSign )
req . URL . RawQuery = query . Encode ( )
// Add signature header to RawQuery.
2017-03-26 14:56:17 -04:00
req . URL . RawQuery += "&X-Amz-Signature=" + url . QueryEscape ( signature )
2016-11-11 00:57:15 -05:00
// Construct the final presigned URL.
return nil
}
2016-09-30 17:32:13 -04:00
// preSignV2 - presign the request in following style.
// https://${S3_BUCKET}.s3.amazonaws.com/${S3_OBJECT}?AWSAccessKeyId=${S3_ACCESS_KEY}&Expires=${TIMESTAMP}&Signature=${SIGNATURE}.
func preSignV2 ( req * http . Request , accessKeyID , secretAccessKey string , expires int64 ) error {
// Presign is not needed for anonymous credentials.
if accessKeyID == "" || secretAccessKey == "" {
return errors . New ( "Presign cannot be generated without access and secret keys" )
}
2017-09-26 14:00:07 -04:00
// FIXME: Remove following portion of code after fixing a bug in minio-go preSignV2.
d := UTCNow ( )
// Find epoch expires when the request will expire.
epochExpires := d . Unix ( ) + expires
// Add expires header if not present.
expiresStr := req . Header . Get ( "Expires" )
if expiresStr == "" {
expiresStr = strconv . FormatInt ( epochExpires , 10 )
req . Header . Set ( "Expires" , expiresStr )
}
// url.RawPath will be valid if path has any encoded characters, if not it will
// be empty - in which case we need to consider url.Path (bug in net/http?)
encodedResource := req . URL . RawPath
encodedQuery := req . URL . RawQuery
if encodedResource == "" {
splits := strings . SplitN ( req . URL . Path , "?" , 2 )
encodedResource = splits [ 0 ]
if len ( splits ) == 2 {
encodedQuery = splits [ 1 ]
}
}
unescapedQueries , err := unescapeQueries ( encodedQuery )
if err != nil {
return err
}
// Get presigned string to sign.
stringToSign := getStringToSignV2 ( req . Method , encodedResource , strings . Join ( unescapedQueries , "&" ) , req . Header , expiresStr )
hm := hmac . New ( sha1 . New , [ ] byte ( secretAccessKey ) )
hm . Write ( [ ] byte ( stringToSign ) )
// Calculate signature.
signature := base64 . StdEncoding . EncodeToString ( hm . Sum ( nil ) )
query := req . URL . Query ( )
// Handle specially for Google Cloud Storage.
query . Set ( "AWSAccessKeyId" , accessKeyID )
// Fill in Expires for presigned query.
query . Set ( "Expires" , strconv . FormatInt ( epochExpires , 10 ) )
// Encode query and save.
req . URL . RawQuery = query . Encode ( )
// Save signature finally.
req . URL . RawQuery += "&Signature=" + url . QueryEscape ( signature )
2016-09-30 17:32:13 -04:00
return nil
}
// Sign given request using Signature V2.
func signRequestV2 ( req * http . Request , accessKey , secretKey string ) error {
2020-04-14 19:53:16 -04:00
signer . SignV2 ( * req , accessKey , secretKey , false )
2016-09-30 17:32:13 -04:00
return nil
}
2016-07-10 14:10:59 -04:00
// Sign given request using Signature V4.
2016-09-30 17:32:13 -04:00
func signRequestV4 ( req * http . Request , accessKey , secretKey string ) error {
2016-07-10 14:10:59 -04:00
// Get hashed payload.
hashedPayload := req . Header . Get ( "x-amz-content-sha256" )
if hashedPayload == "" {
2016-11-15 21:14:23 -05:00
return fmt . Errorf ( "Invalid hashed payload" )
2016-06-21 15:10:18 -04:00
}
2017-03-18 14:28:41 -04:00
currTime := UTCNow ( )
2016-06-21 15:10:18 -04:00
2016-07-10 14:10:59 -04:00
// Set x-amz-date.
req . Header . Set ( "x-amz-date" , currTime . Format ( iso8601Format ) )
2016-06-21 15:10:18 -04:00
2016-07-10 14:10:59 -04:00
// Get header map.
headerMap := make ( map [ string ] [ ] string )
for k , vv := range req . Header {
// If request header key is not in ignored headers, then add it.
if _ , ok := ignoredHeaders [ http . CanonicalHeaderKey ( k ) ] ; ! ok {
headerMap [ strings . ToLower ( k ) ] = vv
2016-06-21 15:10:18 -04:00
}
}
2016-07-10 14:10:59 -04:00
// Get header keys.
headers := [ ] string { "host" }
for k := range headerMap {
headers = append ( headers , k )
2016-06-21 15:10:18 -04:00
}
sort . Strings ( headers )
2021-11-25 16:06:25 -05:00
region := globalSite . Region
2016-09-15 02:53:42 -04:00
2016-07-10 14:10:59 -04:00
// Get canonical headers.
var buf bytes . Buffer
2016-06-21 15:10:18 -04:00
for _ , k := range headers {
2016-07-10 14:10:59 -04:00
buf . WriteString ( k )
buf . WriteByte ( ':' )
2016-06-21 15:10:18 -04:00
switch {
case k == "host" :
2016-07-10 14:10:59 -04:00
buf . WriteString ( req . URL . Host )
2016-06-21 15:10:18 -04:00
fallthrough
default :
2016-07-10 14:10:59 -04:00
for idx , v := range headerMap [ k ] {
2016-06-21 15:10:18 -04:00
if idx > 0 {
2016-07-10 14:10:59 -04:00
buf . WriteByte ( ',' )
2016-06-21 15:10:18 -04:00
}
2016-07-10 14:10:59 -04:00
buf . WriteString ( v )
2016-06-21 15:10:18 -04:00
}
2016-07-10 14:10:59 -04:00
buf . WriteByte ( '\n' )
2016-06-21 15:10:18 -04:00
}
}
2016-07-10 14:10:59 -04:00
canonicalHeaders := buf . String ( )
2016-06-21 15:10:18 -04:00
2016-07-10 14:10:59 -04:00
// Get signed headers.
2016-06-21 15:10:18 -04:00
signedHeaders := strings . Join ( headers , ";" )
2016-07-10 14:10:59 -04:00
// Get canonical query string.
2021-11-16 12:28:29 -05:00
req . URL . RawQuery = strings . ReplaceAll ( req . URL . Query ( ) . Encode ( ) , "+" , "%20" )
2016-06-21 15:10:18 -04:00
2016-07-10 14:10:59 -04:00
// Get canonical URI.
2018-06-05 13:48:51 -04:00
canonicalURI := s3utils . EncodePath ( req . URL . Path )
2016-07-10 14:10:59 -04:00
// Get canonical request.
2016-06-21 15:10:18 -04:00
// canonicalRequest =
// <HTTPMethod>\n
// <CanonicalURI>\n
// <CanonicalQueryString>\n
// <CanonicalHeaders>\n
// <SignedHeaders>\n
// <HashedPayload>
//
canonicalRequest := strings . Join ( [ ] string {
req . Method ,
2016-07-10 14:10:59 -04:00
canonicalURI ,
2016-06-21 15:10:18 -04:00
req . URL . RawQuery ,
2016-07-10 14:10:59 -04:00
canonicalHeaders ,
2016-06-21 15:10:18 -04:00
signedHeaders ,
hashedPayload ,
} , "\n" )
2016-07-10 14:10:59 -04:00
// Get scope.
2016-06-21 15:10:18 -04:00
scope := strings . Join ( [ ] string {
2016-07-10 14:10:59 -04:00
currTime . Format ( yyyymmdd ) ,
2016-09-15 02:53:42 -04:00
region ,
2019-02-27 20:46:55 -05:00
string ( serviceS3 ) ,
2016-06-21 15:10:18 -04:00
"aws4_request" ,
2019-08-06 15:08:58 -04:00
} , SlashSeparator )
2016-06-21 15:10:18 -04:00
2016-07-10 14:10:59 -04:00
stringToSign := "AWS4-HMAC-SHA256" + "\n" + currTime . Format ( iso8601Format ) + "\n"
2016-06-21 15:10:18 -04:00
stringToSign = stringToSign + scope + "\n"
2021-11-16 12:28:29 -05:00
stringToSign += getSHA256Hash ( [ ] byte ( canonicalRequest ) )
2016-06-21 15:10:18 -04:00
2016-07-10 14:10:59 -04:00
date := sumHMAC ( [ ] byte ( "AWS4" + secretKey ) , [ ] byte ( currTime . Format ( yyyymmdd ) ) )
2016-09-15 02:53:42 -04:00
regionHMAC := sumHMAC ( date , [ ] byte ( region ) )
2019-02-27 20:46:55 -05:00
service := sumHMAC ( regionHMAC , [ ] byte ( serviceS3 ) )
2016-06-21 15:10:18 -04:00
signingKey := sumHMAC ( service , [ ] byte ( "aws4_request" ) )
signature := hex . EncodeToString ( sumHMAC ( signingKey , [ ] byte ( stringToSign ) ) )
// final Authorization header
parts := [ ] string {
2019-08-06 15:08:58 -04:00
"AWS4-HMAC-SHA256" + " Credential=" + accessKey + SlashSeparator + scope ,
2016-06-21 15:10:18 -04:00
"SignedHeaders=" + signedHeaders ,
"Signature=" + signature ,
}
auth := strings . Join ( parts , ", " )
req . Header . Set ( "Authorization" , auth )
2016-07-10 14:10:59 -04:00
return nil
}
2016-12-27 11:28:10 -05:00
// getCredentialString generate a credential string.
func getCredentialString ( accessKeyID , location string , t time . Time ) string {
2019-08-06 15:08:58 -04:00
return accessKeyID + SlashSeparator + getScope ( t , location )
2016-09-13 22:00:01 -04:00
}
2018-04-15 09:56:04 -04:00
// getMD5HashBase64 returns MD5 hash in base64 encoding of given data.
func getMD5HashBase64 ( data [ ] byte ) string {
return base64 . StdEncoding . EncodeToString ( getMD5Sum ( data ) )
}
2016-07-10 14:10:59 -04:00
// Returns new HTTP request object.
func newTestRequest ( method , urlStr string , contentLength int64 , body io . ReadSeeker ) ( * http . Request , error ) {
if method == "" {
2020-07-20 15:52:49 -04:00
method = http . MethodPost
2016-07-10 14:10:59 -04:00
}
// Save for subsequent use
var hashedPayload string
2017-08-06 14:27:33 -04:00
var md5Base64 string
2016-07-10 14:10:59 -04:00
switch {
case body == nil :
2016-11-21 16:51:05 -05:00
hashedPayload = getSHA256Hash ( [ ] byte { } )
2016-07-10 14:10:59 -04:00
default :
2016-09-30 17:32:13 -04:00
payloadBytes , err := ioutil . ReadAll ( body )
if err != nil {
return nil , err
2016-07-10 14:10:59 -04:00
}
2016-11-21 16:51:05 -05:00
hashedPayload = getSHA256Hash ( payloadBytes )
2017-08-06 14:27:33 -04:00
md5Base64 = getMD5HashBase64 ( payloadBytes )
2016-07-10 14:10:59 -04:00
}
// Seek back to beginning.
if body != nil {
body . Seek ( 0 , 0 )
} else {
2017-08-06 14:27:33 -04:00
body = bytes . NewReader ( [ ] byte ( "" ) )
2016-07-10 14:10:59 -04:00
}
2017-08-06 14:27:33 -04:00
req , err := http . NewRequest ( method , urlStr , body )
if err != nil {
return nil , err
}
if md5Base64 != "" {
req . Header . Set ( "Content-Md5" , md5Base64 )
}
req . Header . Set ( "x-amz-content-sha256" , hashedPayload )
// Add Content-Length
req . ContentLength = contentLength
2016-07-10 14:10:59 -04:00
return req , nil
}
2016-10-25 16:34:14 -04:00
// Various signature types we are supporting, currently
// two main signature types.
type signerType int
2016-09-30 17:32:13 -04:00
2016-10-25 16:34:14 -04:00
const (
signerV2 signerType = iota
signerV4
)
2016-09-30 17:32:13 -04:00
2016-10-25 16:34:14 -04:00
func newTestSignedRequest ( method , urlStr string , contentLength int64 , body io . ReadSeeker , accessKey , secretKey string , signer signerType ) ( * http . Request , error ) {
if signer == signerV2 {
2018-09-20 22:22:09 -04:00
return newTestSignedRequestV2 ( method , urlStr , contentLength , body , accessKey , secretKey , nil )
2016-09-30 17:32:13 -04:00
}
2018-09-20 22:22:09 -04:00
return newTestSignedRequestV4 ( method , urlStr , contentLength , body , accessKey , secretKey , nil )
2016-09-30 17:32:13 -04:00
}
2017-04-10 12:58:08 -04:00
// Returns request with correct signature but with incorrect SHA256.
func newTestSignedBadSHARequest ( method , urlStr string , contentLength int64 , body io . ReadSeeker , accessKey , secretKey string , signer signerType ) ( * http . Request , error ) {
req , err := newTestRequest ( method , urlStr , contentLength , body )
if err != nil {
return nil , err
}
// Anonymous request return early.
if accessKey == "" || secretKey == "" {
return req , nil
}
if signer == signerV2 {
err = signRequestV2 ( req , accessKey , secretKey )
req . Header . Del ( "x-amz-content-sha256" )
} else {
req . Header . Set ( "x-amz-content-sha256" , "92b165232fbd011da355eca0b033db22b934ba9af0145a437a832d27310b89f9" )
err = signRequestV4 ( req , accessKey , secretKey )
}
return req , err
}
2016-09-30 17:32:13 -04:00
// Returns new HTTP request object signed with signature v2.
2018-09-20 22:22:09 -04:00
func newTestSignedRequestV2 ( method , urlStr string , contentLength int64 , body io . ReadSeeker , accessKey , secretKey string , headers map [ string ] string ) ( * http . Request , error ) {
2016-09-30 17:32:13 -04:00
req , err := newTestRequest ( method , urlStr , contentLength , body )
if err != nil {
return nil , err
}
req . Header . Del ( "x-amz-content-sha256" )
// Anonymous request return quickly.
if accessKey == "" || secretKey == "" {
return req , nil
}
2018-09-20 22:22:09 -04:00
for k , v := range headers {
2020-09-01 19:58:13 -04:00
req . Header . Set ( k , v )
2018-09-20 22:22:09 -04:00
}
2016-09-30 17:32:13 -04:00
err = signRequestV2 ( req , accessKey , secretKey )
if err != nil {
return nil , err
}
return req , nil
}
2016-07-10 14:10:59 -04:00
// Returns new HTTP request object signed with signature v4.
2018-09-20 22:22:09 -04:00
func newTestSignedRequestV4 ( method , urlStr string , contentLength int64 , body io . ReadSeeker , accessKey , secretKey string , headers map [ string ] string ) ( * http . Request , error ) {
2016-07-10 14:10:59 -04:00
req , err := newTestRequest ( method , urlStr , contentLength , body )
if err != nil {
return nil , err
}
2016-09-09 13:18:38 -04:00
// Anonymous request return quickly.
if accessKey == "" || secretKey == "" {
return req , nil
}
2018-09-20 22:22:09 -04:00
for k , v := range headers {
2020-09-01 19:58:13 -04:00
req . Header . Set ( k , v )
2018-09-20 22:22:09 -04:00
}
2016-09-30 17:32:13 -04:00
err = signRequestV4 ( req , accessKey , secretKey )
2016-07-10 14:10:59 -04:00
if err != nil {
return nil , err
}
2016-06-21 15:10:18 -04:00
return req , nil
}
2016-06-25 22:07:44 -04:00
// Function to generate random string for bucket/object names.
func randString ( n int ) string {
2020-09-22 18:34:27 -04:00
src := rand . NewSource ( UTCNow ( ) . UnixNano ( ) )
2016-06-25 22:07:44 -04:00
b := make ( [ ] byte , n )
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
for i , cache , remain := n - 1 , src . Int63 ( ) , letterIdxMax ; i >= 0 ; {
if remain == 0 {
cache , remain = src . Int63 ( ) , letterIdxMax
}
if idx := int ( cache & letterIdxMask ) ; idx < len ( letterBytes ) {
b [ i ] = letterBytes [ idx ]
i --
}
cache >>= letterIdxBits
remain --
}
return string ( b )
}
2016-07-08 17:28:06 -04:00
// generate random object name.
func getRandomObjectName ( ) string {
return randString ( 16 )
}
2016-06-25 22:07:44 -04:00
// generate random bucket name.
func getRandomBucketName ( ) string {
return randString ( 60 )
}
// construct URL for http requests for bucket operations.
func makeTestTargetURL ( endPoint , bucketName , objectName string , queryValues url . Values ) string {
2019-08-06 15:08:58 -04:00
urlStr := endPoint + SlashSeparator
2016-06-25 22:07:44 -04:00
if bucketName != "" {
2019-08-06 15:08:58 -04:00
urlStr = urlStr + bucketName + SlashSeparator
2016-06-25 22:07:44 -04:00
}
if objectName != "" {
2021-11-16 12:28:29 -05:00
urlStr += s3utils . EncodePath ( objectName )
2016-06-25 22:07:44 -04:00
}
if len ( queryValues ) > 0 {
2017-03-26 14:56:17 -04:00
urlStr = urlStr + "?" + queryValues . Encode ( )
2016-06-25 22:07:44 -04:00
}
return urlStr
}
// return URL for uploading object into the bucket.
func getPutObjectURL ( endPoint , bucketName , objectName string ) string {
return makeTestTargetURL ( endPoint , bucketName , objectName , url . Values { } )
}
2016-10-01 11:23:26 -04:00
func getPutObjectPartURL ( endPoint , bucketName , objectName , uploadID , partNumber string ) string {
queryValues := url . Values { }
queryValues . Set ( "uploadId" , uploadID )
queryValues . Set ( "partNumber" , partNumber )
return makeTestTargetURL ( endPoint , bucketName , objectName , queryValues )
}
2017-01-31 12:38:34 -05:00
func getCopyObjectPartURL ( endPoint , bucketName , objectName , uploadID , partNumber string ) string {
queryValues := url . Values { }
queryValues . Set ( "uploadId" , uploadID )
queryValues . Set ( "partNumber" , partNumber )
return makeTestTargetURL ( endPoint , bucketName , objectName , queryValues )
}
2016-06-25 22:07:44 -04:00
// return URL for fetching object from the bucket.
func getGetObjectURL ( endPoint , bucketName , objectName string ) string {
return makeTestTargetURL ( endPoint , bucketName , objectName , url . Values { } )
}
// return URL for deleting the object from the bucket.
func getDeleteObjectURL ( endPoint , bucketName , objectName string ) string {
return makeTestTargetURL ( endPoint , bucketName , objectName , url . Values { } )
}
2016-09-02 04:59:08 -04:00
// return URL for deleting multiple objects from a bucket.
func getMultiDeleteObjectURL ( endPoint , bucketName string ) string {
queryValue := url . Values { }
queryValue . Set ( "delete" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
2016-08-16 22:24:23 -04:00
// return URL for HEAD on the object.
2016-06-25 22:07:44 -04:00
func getHeadObjectURL ( endPoint , bucketName , objectName string ) string {
return makeTestTargetURL ( endPoint , bucketName , objectName , url . Values { } )
}
2016-08-16 22:24:23 -04:00
// return url to be used while copying the object.
func getCopyObjectURL ( endPoint , bucketName , objectName string ) string {
return makeTestTargetURL ( endPoint , bucketName , objectName , url . Values { } )
}
2016-08-05 01:01:58 -04:00
// return URL for inserting bucket notification.
func getPutNotificationURL ( endPoint , bucketName string ) string {
queryValue := url . Values { }
queryValue . Set ( "notification" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
2016-06-25 22:07:44 -04:00
// return URL for inserting bucket policy.
func getPutPolicyURL ( endPoint , bucketName string ) string {
queryValue := url . Values { }
queryValue . Set ( "policy" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
// return URL for fetching bucket policy.
func getGetPolicyURL ( endPoint , bucketName string ) string {
queryValue := url . Values { }
queryValue . Set ( "policy" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
// return URL for deleting bucket policy.
func getDeletePolicyURL ( endPoint , bucketName string ) string {
2016-07-04 01:35:30 -04:00
queryValue := url . Values { }
queryValue . Set ( "policy" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
2016-06-25 22:07:44 -04:00
}
// return URL for creating the bucket.
func getMakeBucketURL ( endPoint , bucketName string ) string {
return makeTestTargetURL ( endPoint , bucketName , "" , url . Values { } )
}
// return URL for listing buckets.
func getListBucketURL ( endPoint string ) string {
return makeTestTargetURL ( endPoint , "" , "" , url . Values { } )
}
// return URL for HEAD on the bucket.
func getHEADBucketURL ( endPoint , bucketName string ) string {
return makeTestTargetURL ( endPoint , bucketName , "" , url . Values { } )
}
// return URL for deleting the bucket.
func getDeleteBucketURL ( endPoint , bucketName string ) string {
return makeTestTargetURL ( endPoint , bucketName , "" , url . Values { } )
2016-09-09 13:18:38 -04:00
}
2016-06-25 22:07:44 -04:00
2016-11-16 12:46:09 -05:00
// return URL for deleting the bucket.
func getDeleteMultipleObjectsURL ( endPoint , bucketName string ) string {
queryValue := url . Values { }
queryValue . Set ( "delete" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
2016-09-09 13:18:38 -04:00
// return URL For fetching location of the bucket.
func getBucketLocationURL ( endPoint , bucketName string ) string {
queryValue := url . Values { }
queryValue . Set ( "location" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
2016-06-25 22:07:44 -04:00
}
2020-03-26 00:06:03 -04:00
// return URL For set/get lifecycle of the bucket.
func getBucketLifecycleURL ( endPoint , bucketName string ) ( ret string ) {
queryValue := url . Values { }
queryValue . Set ( "lifecycle" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
2016-07-17 15:32:05 -04:00
// return URL for listing objects in the bucket with V1 legacy API.
2019-02-24 01:14:24 -05:00
func getListObjectsV1URL ( endPoint , bucketName , prefix , maxKeys , encodingType string ) string {
2016-06-28 02:54:56 -04:00
queryValue := url . Values { }
if maxKeys != "" {
queryValue . Set ( "max-keys" , maxKeys )
}
2019-02-24 01:14:24 -05:00
if encodingType != "" {
queryValue . Set ( "encoding-type" , encodingType )
}
return makeTestTargetURL ( endPoint , bucketName , prefix , queryValue )
2016-06-28 02:54:56 -04:00
}
2016-07-17 15:32:05 -04:00
// return URL for listing objects in the bucket with V2 API.
2019-02-24 01:14:24 -05:00
func getListObjectsV2URL ( endPoint , bucketName , prefix , maxKeys , fetchOwner , encodingType string ) string {
2016-07-17 15:32:05 -04:00
queryValue := url . Values { }
queryValue . Set ( "list-type" , "2" ) // Enables list objects V2 URL.
if maxKeys != "" {
queryValue . Set ( "max-keys" , maxKeys )
}
2016-09-10 13:44:38 -04:00
if fetchOwner != "" {
queryValue . Set ( "fetch-owner" , fetchOwner )
}
2019-02-24 01:14:24 -05:00
if encodingType != "" {
queryValue . Set ( "encoding-type" , encodingType )
}
return makeTestTargetURL ( endPoint , bucketName , prefix , queryValue )
2016-07-17 15:32:05 -04:00
}
2016-06-28 02:54:56 -04:00
// return URL for a new multipart upload.
func getNewMultipartURL ( endPoint , bucketName , objectName string ) string {
queryValue := url . Values { }
queryValue . Set ( "uploads" , "" )
return makeTestTargetURL ( endPoint , bucketName , objectName , queryValue )
}
// return URL for a new multipart upload.
func getPartUploadURL ( endPoint , bucketName , objectName , uploadID , partNumber string ) string {
queryValues := url . Values { }
queryValues . Set ( "uploadId" , uploadID )
queryValues . Set ( "partNumber" , partNumber )
return makeTestTargetURL ( endPoint , bucketName , objectName , queryValues )
}
// return URL for aborting multipart upload.
func getAbortMultipartUploadURL ( endPoint , bucketName , objectName , uploadID string ) string {
queryValue := url . Values { }
queryValue . Set ( "uploadId" , uploadID )
return makeTestTargetURL ( endPoint , bucketName , objectName , queryValue )
}
2016-09-13 22:00:01 -04:00
// return URL for a listing pending multipart uploads.
2016-06-28 02:54:56 -04:00
func getListMultipartURL ( endPoint , bucketName string ) string {
queryValue := url . Values { }
queryValue . Set ( "uploads" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
2016-09-13 22:00:01 -04:00
// return URL for listing pending multipart uploads with parameters.
func getListMultipartUploadsURLWithParams ( endPoint , bucketName , prefix , keyMarker , uploadIDMarker , delimiter , maxUploads string ) string {
queryValue := url . Values { }
queryValue . Set ( "uploads" , "" )
queryValue . Set ( "prefix" , prefix )
queryValue . Set ( "delimiter" , delimiter )
queryValue . Set ( "key-marker" , keyMarker )
queryValue . Set ( "upload-id-marker" , uploadIDMarker )
queryValue . Set ( "max-uploads" , maxUploads )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
// return URL for a listing parts on a given upload id.
2016-10-03 11:54:57 -04:00
func getListMultipartURLWithParams ( endPoint , bucketName , objectName , uploadID , maxParts , partNumberMarker , encoding string ) string {
2016-06-28 02:54:56 -04:00
queryValues := url . Values { }
queryValues . Set ( "uploadId" , uploadID )
queryValues . Set ( "max-parts" , maxParts )
2016-10-03 11:54:57 -04:00
if partNumberMarker != "" {
queryValues . Set ( "part-number-marker" , partNumberMarker )
}
2016-06-28 02:54:56 -04:00
return makeTestTargetURL ( endPoint , bucketName , objectName , queryValues )
}
// return URL for completing multipart upload.
// complete multipart upload request is sent after all parts are uploaded.
func getCompleteMultipartUploadURL ( endPoint , bucketName , objectName , uploadID string ) string {
queryValue := url . Values { }
queryValue . Set ( "uploadId" , uploadID )
return makeTestTargetURL ( endPoint , bucketName , objectName , queryValue )
}
2016-09-28 04:08:03 -04:00
// return URL for listen bucket notification.
2020-07-20 15:52:49 -04:00
func getListenNotificationURL ( endPoint , bucketName string , prefixes , suffixes , events [ ] string ) string {
2016-09-28 04:08:03 -04:00
queryValue := url . Values { }
2016-10-12 14:02:15 -04:00
queryValue [ "prefix" ] = prefixes
queryValue [ "suffix" ] = suffixes
2016-09-28 04:08:03 -04:00
queryValue [ "events" ] = events
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
2016-06-21 15:10:18 -04:00
// returns temp root directory. `
func getTestRoot ( ) ( string , error ) {
2016-12-16 01:25:05 -05:00
return ioutil . TempDir ( globalTestTmpDir , "api-" )
2016-06-21 15:10:18 -04:00
}
2016-08-30 22:22:27 -04:00
// getRandomDisks - Creates a slice of N random disks, each of the form - minio-XXX
func getRandomDisks ( N int ) ( [ ] string , error ) {
2016-06-07 21:15:04 -04:00
var erasureDisks [ ] string
2016-08-30 22:22:27 -04:00
for i := 0 ; i < N ; i ++ {
2016-12-16 01:25:05 -05:00
path , err := ioutil . TempDir ( globalTestTmpDir , "minio-" )
2016-05-06 14:57:04 -04:00
if err != nil {
2016-08-30 22:22:27 -04:00
// Remove directories created so far.
removeRoots ( erasureDisks )
return nil , err
2016-05-06 14:57:04 -04:00
}
2016-06-07 21:15:04 -04:00
erasureDisks = append ( erasureDisks , path )
2016-05-06 14:57:04 -04:00
}
2016-08-30 22:22:27 -04:00
return erasureDisks , nil
}
2016-05-06 14:57:04 -04:00
2018-02-15 20:45:57 -05:00
// Initialize object layer with the supplied disks, objectLayer is nil upon any error.
2020-12-01 16:50:33 -05:00
func newTestObjectLayer ( ctx context . Context , endpointServerPools EndpointServerPools ) ( newObject ObjectLayer , err error ) {
2018-02-15 20:45:57 -05:00
// For FS only, directly use the disk.
2020-12-01 16:50:33 -05:00
if endpointServerPools . NEndpoints ( ) == 1 {
2018-02-15 20:45:57 -05:00
// Initialize new FS object layer.
2020-12-01 16:50:33 -05:00
return NewFSObjectLayer ( endpointServerPools [ 0 ] . Endpoints [ 0 ] . Path )
2018-02-15 20:45:57 -05:00
}
2020-12-01 16:50:33 -05:00
z , err := newErasureServerPools ( ctx , endpointServerPools )
2019-11-19 20:42:27 -05:00
if err != nil {
return nil , err
2019-11-13 15:17:45 -05:00
}
2018-02-15 20:45:57 -05:00
2020-05-19 16:53:54 -04:00
newAllSubsystems ( )
2019-11-09 12:27:23 -05:00
2019-11-21 07:24:51 -05:00
return z , nil
2018-02-15 20:45:57 -05:00
}
// initObjectLayer - Instantiates object layer and returns it.
2020-12-01 16:50:33 -05:00
func initObjectLayer ( ctx context . Context , endpointServerPools EndpointServerPools ) ( ObjectLayer , [ ] StorageAPI , error ) {
objLayer , err := newTestObjectLayer ( ctx , endpointServerPools )
2016-10-05 15:48:07 -04:00
if err != nil {
return nil , nil , err
2016-06-07 21:15:04 -04:00
}
2016-10-05 15:48:07 -04:00
2018-02-15 20:45:57 -05:00
var formattedDisks [ ] StorageAPI
// Should use the object layer tests for validating cache.
2020-12-01 16:50:33 -05:00
if z , ok := objLayer . ( * erasureServerPools ) ; ok {
formattedDisks = z . serverPools [ 0 ] . GetDisks ( 0 ) ( )
2018-02-15 20:45:57 -05:00
}
2016-10-05 15:48:07 -04:00
// Success.
2016-11-23 18:48:10 -05:00
return objLayer , formattedDisks , nil
2016-06-07 21:15:04 -04:00
}
// removeRoots - Cleans up initialized directories during tests.
func removeRoots ( roots [ ] string ) {
for _ , root := range roots {
2017-08-12 22:25:43 -04:00
os . RemoveAll ( root )
2016-05-06 14:57:04 -04:00
}
2016-06-07 21:15:04 -04:00
}
2016-05-06 14:57:04 -04:00
2021-11-16 12:28:29 -05:00
// removeDiskN - removes N disks from supplied disk slice.
2016-06-29 01:32:00 -04:00
func removeDiskN ( disks [ ] string , n int ) {
if n > len ( disks ) {
n = len ( disks )
}
for _ , disk := range disks [ : n ] {
2017-08-12 22:25:43 -04:00
os . RemoveAll ( disk )
2016-06-07 21:15:04 -04:00
}
}
2016-09-10 17:47:27 -04:00
// creates a bucket for the tests and returns the bucket name.
// initializes the specified API endpoints for the tests.
// initialies the root and returns its path.
// return credentials.
2021-11-12 00:03:02 -05:00
func initAPIHandlerTest ( ctx context . Context , obj ObjectLayer , endpoints [ ] string ) ( string , http . Handler , error ) {
2020-05-19 16:53:54 -04:00
newAllSubsystems ( )
2021-11-17 16:42:08 -05:00
initConfigSubsystem ( ctx , obj )
2020-05-19 16:53:54 -04:00
2021-11-29 17:38:57 -05:00
globalIAMSys . Init ( ctx , obj , globalEtcdClient , globalNotificationSys , 2 * time . Second )
2021-05-09 11:14:19 -04:00
2016-09-10 17:47:27 -04:00
// get random bucket name.
2017-01-23 20:01:44 -05:00
bucketName := getRandomBucketName ( )
2016-09-10 17:47:27 -04:00
// Create bucket.
2020-06-12 23:04:01 -04:00
err := obj . MakeBucketWithLocation ( context . Background ( ) , bucketName , BucketOptions { } )
2016-09-10 17:47:27 -04:00
if err != nil {
// failed to create newbucket, return err.
2016-10-21 04:25:17 -04:00
return "" , nil , err
2016-09-10 17:47:27 -04:00
}
2020-06-12 23:04:01 -04:00
// Register the API end points with Erasure object layer.
2016-09-10 17:47:27 -04:00
// Registering only the GetObject handler.
2017-01-23 20:01:44 -05:00
apiRouter := initTestAPIEndPoints ( obj , endpoints )
2019-02-13 07:59:36 -05:00
f := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2017-01-23 20:01:44 -05:00
r . RequestURI = r . URL . RequestURI ( )
apiRouter . ServeHTTP ( w , r )
2019-02-13 07:59:36 -05:00
} )
2017-01-23 20:01:44 -05:00
return bucketName , f , nil
2016-09-10 17:47:27 -04:00
}
2017-02-28 21:05:52 -05:00
// prepare test backend.
2020-06-12 23:04:01 -04:00
// create FS/Erasure/ErasureSet backend.
2017-02-28 21:05:52 -05:00
// return object layer, backend disks.
2020-04-14 20:52:38 -04:00
func prepareTestBackend ( ctx context . Context , instanceType string ) ( ObjectLayer , [ ] string , error ) {
2017-02-28 21:05:52 -05:00
switch instanceType {
2020-06-12 23:04:01 -04:00
// Total number of disks for Erasure sets backend is set to 32.
case ErasureSetsTestStr :
return prepareErasureSets32 ( ctx )
// Total number of disks for Erasure backend is set to 16.
case ErasureTestStr :
return prepareErasure16 ( ctx )
2017-02-28 21:05:52 -05:00
default :
// return FS backend by default.
obj , disk , err := prepareFS ( )
if err != nil {
return nil , nil , err
}
return obj , [ ] string { disk } , nil
}
}
2016-10-11 23:38:10 -04:00
// ExecObjectLayerAPIAnonTest - Helper function to validate object Layer API handler
// response for anonymous/unsigned and unknown signature type HTTP request.
2016-10-07 14:16:11 -04:00
// Here is the brief description of some of the arguments to the function below.
// apiRouter - http.Handler with the relevant API endPoint (API endPoint under test) registered.
// anonReq - unsigned *http.Request to invoke the handler's response for anonymous requests.
// policyFunc - function to return bucketPolicy statement which would permit the anonymous request to be served.
// The test works in 2 steps, here is the description of the steps.
// STEP 1: Call the handler with the unsigned HTTP request (anonReq), assert for the `ErrAccessDenied` error response.
2018-02-09 18:19:30 -05:00
func ExecObjectLayerAPIAnonTest ( t * testing . T , obj ObjectLayer , testName , bucketName , objectName , instanceType string , apiRouter http . Handler ,
2022-01-02 12:15:06 -05:00
anonReq * http . Request , bucketPolicy * policy . Policy ,
) {
2016-10-11 23:38:10 -04:00
anonTestStr := "Anonymous HTTP request test"
unknownSignTestStr := "Unknown HTTP signature test"
2016-11-21 02:41:39 -05:00
// simple function which returns a message which gives the context of the test
2016-10-07 14:16:11 -04:00
// and then followed by the the actual error message.
2016-11-21 02:41:39 -05:00
failTestStr := func ( testType , failMsg string ) string {
2019-04-09 14:39:42 -04:00
return fmt . Sprintf ( "MinIO %s: %s fail for \"%s\": \n<Error> %s" , instanceType , testType , testName , failMsg )
2016-10-07 14:16:11 -04:00
}
2017-02-07 15:51:43 -05:00
2016-10-07 14:16:11 -04:00
// httptest Recorder to capture all the response by the http handler.
rec := httptest . NewRecorder ( )
// reading the body to preserve it so that it can be used again for second attempt of sending unsigned HTTP request.
// If the body is read in the handler the same request cannot be made use of.
buf , err := ioutil . ReadAll ( anonReq . Body )
if err != nil {
2016-11-21 02:41:39 -05:00
t . Fatal ( failTestStr ( anonTestStr , err . Error ( ) ) )
2016-10-07 14:16:11 -04:00
}
2016-10-09 12:21:37 -04:00
2016-10-07 14:16:11 -04:00
// creating 2 read closer (to set as request body) from the body content.
readerOne := ioutil . NopCloser ( bytes . NewBuffer ( buf ) )
readerTwo := ioutil . NopCloser ( bytes . NewBuffer ( buf ) )
anonReq . Body = readerOne
// call the HTTP handler.
apiRouter . ServeHTTP ( rec , anonReq )
// expected error response when the unsigned HTTP request is not permitted.
2020-04-02 15:35:22 -04:00
accessDenied := getAPIError ( ErrAccessDenied ) . HTTPStatusCode
if rec . Code != accessDenied {
t . Fatal ( failTestStr ( anonTestStr , fmt . Sprintf ( "Object API Nil Test expected to fail with %d, but failed with %d" , accessDenied , rec . Code ) ) )
2016-10-07 14:16:11 -04:00
}
2016-10-09 12:21:37 -04:00
// HEAD HTTTP request doesn't contain response body.
2020-07-20 15:52:49 -04:00
if anonReq . Method != http . MethodHead {
2016-10-09 12:21:37 -04:00
// read the response body.
2018-04-24 18:53:30 -04:00
var actualContent [ ] byte
actualContent , err = ioutil . ReadAll ( rec . Body )
2016-10-09 12:21:37 -04:00
if err != nil {
2016-11-21 02:41:39 -05:00
t . Fatal ( failTestStr ( anonTestStr , fmt . Sprintf ( "Failed parsing response body: <ERROR> %v" , err ) ) )
2016-10-09 12:21:37 -04:00
}
2019-02-13 19:07:21 -05:00
actualError := & APIErrorResponse { }
if err = xml . Unmarshal ( actualContent , actualError ) ; err != nil {
t . Fatal ( failTestStr ( anonTestStr , "error response failed to parse error XML" ) )
}
if actualError . BucketName != bucketName {
t . Fatal ( failTestStr ( anonTestStr , "error response bucket name differs from expected value" ) )
}
if actualError . Key != objectName {
t . Fatal ( failTestStr ( anonTestStr , "error response object name differs from expected value" ) )
2016-10-09 12:21:37 -04:00
}
2016-10-07 14:16:11 -04:00
}
2018-04-24 18:53:30 -04:00
2016-10-11 23:38:10 -04:00
// test for unknown auth case.
2020-05-19 16:53:54 -04:00
anonReq . Body = readerTwo
2016-10-11 23:38:10 -04:00
// Setting the `Authorization` header to a random value so that the signature falls into unknown auth case.
anonReq . Header . Set ( "Authorization" , "nothingElse" )
// initialize new response recorder.
rec = httptest . NewRecorder ( )
// call the handler using the HTTP Request.
apiRouter . ServeHTTP ( rec , anonReq )
// verify the response body for `ErrAccessDenied` message =.
2020-07-20 15:52:49 -04:00
if anonReq . Method != http . MethodHead {
2016-10-11 23:38:10 -04:00
// read the response body.
actualContent , err := ioutil . ReadAll ( rec . Body )
if err != nil {
2016-11-21 02:41:39 -05:00
t . Fatal ( failTestStr ( unknownSignTestStr , fmt . Sprintf ( "Failed parsing response body: <ERROR> %v" , err ) ) )
2016-10-11 23:38:10 -04:00
}
2019-02-13 19:07:21 -05:00
actualError := & APIErrorResponse { }
if err = xml . Unmarshal ( actualContent , actualError ) ; err != nil {
t . Fatal ( failTestStr ( unknownSignTestStr , "error response failed to parse error XML" ) )
}
2021-08-08 01:43:01 -04:00
if path . Clean ( actualError . Resource ) != pathJoin ( SlashSeparator , bucketName , SlashSeparator , objectName ) {
t . Fatal ( failTestStr ( unknownSignTestStr , "error response resource differs from expected value" ) )
2016-10-11 23:38:10 -04:00
}
}
2020-04-02 15:35:22 -04:00
// expected error response when the unsigned HTTP request is not permitted.
unsupportedSignature := getAPIError ( ErrSignatureVersionNotSupported ) . HTTPStatusCode
if rec . Code != unsupportedSignature {
t . Fatal ( failTestStr ( unknownSignTestStr , fmt . Sprintf ( "Object API Unknow auth test for \"%s\", expected to fail with %d, but failed with %d" , testName , unsupportedSignature , rec . Code ) ) )
2016-10-11 23:38:10 -04:00
}
2016-10-07 14:16:11 -04:00
}
2017-01-31 12:38:34 -05:00
// ExecObjectLayerAPINilTest - Sets the object layer to `nil`, and calls rhe registered object layer API endpoint,
// and assert the error response. The purpose is to validate the API handlers response when the object layer is uninitialized.
// Usage hint: Should be used at the end of the API end points tests (ex: check the last few lines of `testAPIListObjectPartsHandler`),
// need a sample HTTP request to be sent as argument so that the relevant handler is called, the handler registration is expected
// to be done since its called from within the API handler tests, the reference to the registered HTTP handler has to be sent
// as an argument.
2016-10-06 16:34:33 -04:00
func ExecObjectLayerAPINilTest ( t TestErrHandler , bucketName , objectName , instanceType string , apiRouter http . Handler , req * http . Request ) {
// httptest Recorder to capture all the response by the http handler.
rec := httptest . NewRecorder ( )
// The API handler gets the referece to the object layer via the global object Layer,
// setting it to `nil` in order test for handlers response for uninitialized object layer.
2016-10-10 02:03:10 -04:00
globalObjLayerMutex . Lock ( )
2016-10-06 16:34:33 -04:00
globalObjectAPI = nil
2016-10-10 02:03:10 -04:00
globalObjLayerMutex . Unlock ( )
2017-01-31 12:38:34 -05:00
2016-10-06 16:34:33 -04:00
// call the HTTP handler.
apiRouter . ServeHTTP ( rec , req )
// expected error response when the API handler is called before the object layer is initialized,
// or when objectLayer is `nil`.
serverNotInitializedErr := getAPIError ( ErrServerNotInitialized ) . HTTPStatusCode
if rec . Code != serverNotInitializedErr {
2016-11-15 21:14:23 -05:00
t . Errorf ( "Object API Nil Test expected to fail with %d, but failed with %d" , serverNotInitializedErr , rec . Code )
2016-10-06 16:34:33 -04:00
}
2016-10-11 03:00:02 -04:00
// HEAD HTTP Request doesn't contain body in its response,
// for other type of HTTP requests compare the response body content with the expected one.
2020-07-20 15:52:49 -04:00
if req . Method != http . MethodHead {
2016-10-11 03:00:02 -04:00
// read the response body.
actualContent , err := ioutil . ReadAll ( rec . Body )
if err != nil {
2019-04-09 14:39:42 -04:00
t . Fatalf ( "MinIO %s: Failed parsing response body: <ERROR> %v" , instanceType , err )
2016-10-11 03:00:02 -04:00
}
2019-02-13 19:07:21 -05:00
actualError := & APIErrorResponse { }
if err = xml . Unmarshal ( actualContent , actualError ) ; err != nil {
2019-04-09 14:39:42 -04:00
t . Errorf ( "MinIO %s: error response failed to parse error XML" , instanceType )
2019-02-13 19:07:21 -05:00
}
if actualError . BucketName != bucketName {
2019-04-09 14:39:42 -04:00
t . Errorf ( "MinIO %s: error response bucket name differs from expected value" , instanceType )
2019-02-13 19:07:21 -05:00
}
if actualError . Key != objectName {
2019-04-09 14:39:42 -04:00
t . Errorf ( "MinIO %s: error response object name differs from expected value" , instanceType )
2016-10-11 03:00:02 -04:00
}
2016-10-06 16:34:33 -04:00
}
}
2016-09-10 17:47:27 -04:00
// ExecObjectLayerAPITest - executes object layer API tests.
2020-06-12 23:04:01 -04:00
// Creates single node and Erasure ObjectLayer instance, registers the specified API end points and runs test for both the layers.
2016-10-27 06:30:52 -04:00
func ExecObjectLayerAPITest ( t * testing . T , objAPITest objAPITestType , endpoints [ ] string ) {
2020-04-14 20:52:38 -04:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2017-01-07 14:27:01 -05:00
// reset globals.
// this is to make sure that the tests are not affected by modified value.
resetTestGlobals ( )
2017-02-07 15:51:43 -05:00
2016-09-10 17:47:27 -04:00
objLayer , fsDir , err := prepareFS ( )
if err != nil {
t . Fatalf ( "Initialization of object layer failed for single node setup: %s" , err )
}
2020-05-19 16:53:54 -04:00
2021-11-12 00:03:02 -05:00
bucketFS , fsAPIRouter , err := initAPIHandlerTest ( ctx , objLayer , endpoints )
2016-09-10 17:47:27 -04:00
if err != nil {
2018-10-17 20:25:50 -04:00
t . Fatalf ( "Initialization of API handler tests failed: <ERROR> %s" , err )
2016-09-10 17:47:27 -04:00
}
2018-08-15 00:41:47 -04:00
// initialize the server and obtain the credentials and root.
// credentials are necessary to sign the HTTP request.
if err = newTestConfig ( globalMinioDefaultRegion , objLayer ) ; err != nil {
t . Fatalf ( "Unable to initialize server config. %s" , err )
}
2019-10-23 01:59:13 -04:00
credentials := globalActiveCred
2018-08-15 00:41:47 -04:00
2016-09-10 17:47:27 -04:00
// Executing the object layer tests for single node setup.
2016-11-01 13:21:16 -04:00
objAPITest ( objLayer , FSTestStr , bucketFS , fsAPIRouter , credentials , t )
2016-09-10 17:47:27 -04:00
2020-06-12 23:04:01 -04:00
objLayer , erasureDisks , err := prepareErasure16 ( ctx )
2016-09-10 17:47:27 -04:00
if err != nil {
2020-06-12 23:04:01 -04:00
t . Fatalf ( "Initialization of object layer failed for Erasure setup: %s" , err )
2016-09-10 17:47:27 -04:00
}
2020-09-10 12:18:19 -04:00
defer objLayer . Shutdown ( ctx )
2020-05-19 16:53:54 -04:00
2021-11-12 00:03:02 -05:00
bucketErasure , erAPIRouter , err := initAPIHandlerTest ( ctx , objLayer , endpoints )
2016-09-10 17:47:27 -04:00
if err != nil {
t . Fatalf ( "Initialzation of API handler tests failed: <ERROR> %s" , err )
}
2020-06-12 23:04:01 -04:00
// Executing the object layer tests for Erasure.
objAPITest ( objLayer , ErasureTestStr , bucketErasure , erAPIRouter , credentials , t )
2016-10-06 16:34:33 -04:00
// clean up the temporary test backend.
2020-06-12 23:04:01 -04:00
removeRoots ( append ( erasureDisks , fsDir ) )
2016-09-10 17:47:27 -04:00
}
2021-01-05 23:08:35 -05:00
// ExecExtendedObjectLayerTest will execute the tests with combinations of encrypted & compressed.
// This can be used to test functionality when reading and writing data.
func ExecExtendedObjectLayerAPITest ( t * testing . T , objAPITest objAPITestType , endpoints [ ] string ) {
execExtended ( t , func ( t * testing . T ) {
ExecObjectLayerAPITest ( t , objAPITest , endpoints )
} )
}
2016-09-10 17:47:27 -04:00
// function to be passed to ExecObjectLayerAPITest, for executing object layr API handler tests.
type objAPITestType func ( obj ObjectLayer , instanceType string , bucketName string ,
2017-10-31 14:54:32 -04:00
apiRouter http . Handler , credentials auth . Credentials , t * testing . T )
2016-09-10 17:47:27 -04:00
2016-06-07 21:15:04 -04:00
// Regular object test type.
2016-07-07 18:05:51 -04:00
type objTestType func ( obj ObjectLayer , instanceType string , t TestErrHandler )
2016-06-07 21:15:04 -04:00
2017-12-22 06:28:13 -05:00
// Special test type for test with directories
type objTestTypeWithDirs func ( obj ObjectLayer , instanceType string , dirs [ ] string , t TestErrHandler )
2016-06-07 21:15:04 -04:00
// Special object test type for disk not found situations.
type objTestDiskNotFoundType func ( obj ObjectLayer , instanceType string , dirs [ ] string , t * testing . T )
// ExecObjectLayerTest - executes object layer tests.
2020-06-12 23:04:01 -04:00
// Creates single node and Erasure ObjectLayer instance and runs test for both the layers.
2016-07-07 18:05:51 -04:00
func ExecObjectLayerTest ( t TestErrHandler , objTest objTestType ) {
2021-11-12 00:03:02 -05:00
{
ctx , cancel := context . WithCancel ( context . Background ( ) )
if localMetacacheMgr != nil {
localMetacacheMgr . deleteAll ( )
}
2018-08-15 00:41:47 -04:00
2021-11-12 00:03:02 -05:00
objLayer , fsDir , err := prepareFS ( )
if err != nil {
t . Fatalf ( "Initialization of object layer failed for single node setup: %s" , err )
}
setObjectLayer ( objLayer )
2020-02-05 04:42:34 -05:00
2021-11-12 00:03:02 -05:00
newAllSubsystems ( )
2021-05-09 11:14:19 -04:00
2021-11-12 00:03:02 -05:00
// initialize the server and obtain the credentials and root.
// credentials are necessary to sign the HTTP request.
if err = newTestConfig ( globalMinioDefaultRegion , objLayer ) ; err != nil {
t . Fatal ( "Unexpected error" , err )
}
2021-11-17 16:42:08 -05:00
initConfigSubsystem ( ctx , objLayer )
2021-11-29 17:38:57 -05:00
globalIAMSys . Init ( ctx , objLayer , globalEtcdClient , globalNotificationSys , 2 * time . Second )
2016-05-20 23:48:47 -04:00
2021-11-12 00:03:02 -05:00
// Executing the object layer tests for single node setup.
objTest ( objLayer , FSTestStr , t )
2020-10-30 12:33:16 -04:00
2021-11-12 00:03:02 -05:00
// Call clean up functions
cancel ( )
setObjectLayer ( newObjectLayerFn ( ) )
removeRoots ( [ ] string { fsDir } )
2016-05-06 14:57:04 -04:00
}
2020-10-28 12:18:35 -04:00
2021-11-12 00:03:02 -05:00
{
ctx , cancel := context . WithCancel ( context . Background ( ) )
2020-05-19 16:53:54 -04:00
2021-11-12 00:03:02 -05:00
if localMetacacheMgr != nil {
localMetacacheMgr . deleteAll ( )
}
2020-05-19 16:53:54 -04:00
2021-11-12 00:03:02 -05:00
newAllSubsystems ( )
objLayer , fsDirs , err := prepareErasureSets32 ( ctx )
if err != nil {
t . Fatalf ( "Initialization of object layer failed for Erasure setup: %s" , err )
}
setObjectLayer ( objLayer )
2021-11-17 16:42:08 -05:00
initConfigSubsystem ( ctx , objLayer )
2021-11-29 17:38:57 -05:00
globalIAMSys . Init ( ctx , objLayer , globalEtcdClient , globalNotificationSys , 2 * time . Second )
2021-05-09 11:14:19 -04:00
2021-11-12 00:03:02 -05:00
// Executing the object layer tests for Erasure.
objTest ( objLayer , ErasureTestStr , t )
2020-10-30 12:33:16 -04:00
2021-11-12 00:03:02 -05:00
objLayer . Shutdown ( context . Background ( ) )
if localMetacacheMgr != nil {
localMetacacheMgr . deleteAll ( )
}
setObjectLayer ( newObjectLayerFn ( ) )
cancel ( )
removeRoots ( fsDirs )
2020-10-30 12:33:16 -04:00
}
2016-05-06 14:57:04 -04:00
}
2016-06-07 21:15:04 -04:00
2017-12-22 06:28:13 -05:00
// ExecObjectLayerTestWithDirs - executes object layer tests.
2020-06-12 23:04:01 -04:00
// Creates single node and Erasure ObjectLayer instance and runs test for both the layers.
2017-12-22 06:28:13 -05:00
func ExecObjectLayerTestWithDirs ( t TestErrHandler , objTest objTestTypeWithDirs ) {
2020-04-14 20:52:38 -04:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2020-06-12 23:04:01 -04:00
objLayer , fsDirs , err := prepareErasure16 ( ctx )
2017-12-22 06:28:13 -05:00
if err != nil {
2020-06-12 23:04:01 -04:00
t . Fatalf ( "Initialization of object layer failed for Erasure setup: %s" , err )
2017-12-22 06:28:13 -05:00
}
2020-09-10 12:18:19 -04:00
defer objLayer . Shutdown ( ctx )
2017-12-22 06:28:13 -05:00
2018-08-15 00:41:47 -04:00
// initialize the server and obtain the credentials and root.
// credentials are necessary to sign the HTTP request.
if err = newTestConfig ( globalMinioDefaultRegion , objLayer ) ; err != nil {
t . Fatal ( "Unexpected error" , err )
2017-12-22 06:28:13 -05:00
}
2020-06-12 23:04:01 -04:00
// Executing the object layer tests for Erasure.
objTest ( objLayer , ErasureTestStr , fsDirs , t )
2018-08-15 00:41:47 -04:00
defer removeRoots ( fsDirs )
2017-12-22 06:28:13 -05:00
}
2016-10-20 19:09:55 -04:00
// ExecObjectLayerDiskAlteredTest - executes object layer tests while altering
2020-06-12 23:04:01 -04:00
// disks in between tests. Creates Erasure ObjectLayer instance and runs test for Erasure layer.
2016-10-20 19:09:55 -04:00
func ExecObjectLayerDiskAlteredTest ( t * testing . T , objTest objTestDiskNotFoundType ) {
2020-04-14 20:52:38 -04:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2020-06-12 23:04:01 -04:00
objLayer , fsDirs , err := prepareErasure16 ( ctx )
2016-06-07 21:15:04 -04:00
if err != nil {
2020-06-12 23:04:01 -04:00
t . Fatalf ( "Initialization of object layer failed for Erasure setup: %s" , err )
2016-06-07 21:15:04 -04:00
}
2020-09-10 12:18:19 -04:00
defer objLayer . Shutdown ( ctx )
2017-02-07 15:51:43 -05:00
2018-08-15 00:41:47 -04:00
if err = newTestConfig ( globalMinioDefaultRegion , objLayer ) ; err != nil {
t . Fatal ( "Failed to create config directory" , err )
}
2020-06-12 23:04:01 -04:00
// Executing the object layer tests for Erasure.
objTest ( objLayer , ErasureTestStr , fsDirs , t )
2016-06-07 21:15:04 -04:00
defer removeRoots ( fsDirs )
}
2016-06-29 05:28:46 -04:00
// Special object test type for stale files situations.
type objTestStaleFilesType func ( obj ObjectLayer , instanceType string , dirs [ ] string , t * testing . T )
// ExecObjectLayerStaleFilesTest - executes object layer tests those leaves stale
2020-06-12 23:04:01 -04:00
// files/directories under .minio/tmp. Creates Erasure ObjectLayer instance and runs test for Erasure layer.
2016-06-29 05:28:46 -04:00
func ExecObjectLayerStaleFilesTest ( t * testing . T , objTest objTestStaleFilesType ) {
2020-04-14 20:52:38 -04:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2016-08-30 22:22:27 -04:00
nDisks := 16
erasureDisks , err := getRandomDisks ( nDisks )
if err != nil {
2020-06-12 23:04:01 -04:00
t . Fatalf ( "Initialization of disks for Erasure setup: %s" , err )
2016-08-30 22:22:27 -04:00
}
2021-01-26 23:47:42 -05:00
objLayer , _ , err := initObjectLayer ( ctx , mustGetPoolEndpoints ( erasureDisks ... ) )
2016-06-29 05:28:46 -04:00
if err != nil {
2020-06-12 23:04:01 -04:00
t . Fatalf ( "Initialization of object layer failed for Erasure setup: %s" , err )
2016-06-29 05:28:46 -04:00
}
2018-08-15 00:41:47 -04:00
if err = newTestConfig ( globalMinioDefaultRegion , objLayer ) ; err != nil {
t . Fatal ( "Failed to create config directory" , err )
}
2020-06-12 23:04:01 -04:00
// Executing the object layer tests for Erasure.
objTest ( objLayer , ErasureTestStr , erasureDisks , t )
2016-08-30 22:22:27 -04:00
defer removeRoots ( erasureDisks )
2016-06-29 05:28:46 -04:00
}
2016-07-02 22:05:16 -04:00
2018-04-21 22:23:54 -04:00
func registerBucketLevelFunc ( bucket * mux . Router , api objectAPIHandlers , apiFunctions ... string ) {
2016-10-05 15:48:07 -04:00
for _ , apiFunction := range apiFunctions {
switch apiFunction {
case "PostPolicy" :
// Register PostPolicy handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPost ) . HeadersRegexp ( "Content-Type" , "multipart/form-data*" ) . HandlerFunc ( api . PostPolicyBucketHandler )
2016-11-07 19:02:27 -05:00
case "HeadObject" :
// Register HeadObject handler.
bucket . Methods ( "Head" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . HeadObjectHandler )
2016-10-05 15:48:07 -04:00
case "GetObject" :
2016-11-07 19:02:27 -05:00
// Register GetObject handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodGet ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . GetObjectHandler )
2016-10-05 15:48:07 -04:00
case "PutObject" :
2016-11-07 19:02:27 -05:00
// Register PutObject handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPut ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . PutObjectHandler )
2016-10-05 15:48:07 -04:00
case "DeleteObject" :
2016-11-07 19:02:27 -05:00
// Register Delete Object handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodDelete ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . DeleteObjectHandler )
2016-10-05 15:48:07 -04:00
case "CopyObject" :
2016-11-07 19:02:27 -05:00
// Register Copy Object handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPut ) . Path ( "/{object:.+}" ) . HeadersRegexp ( "X-Amz-Copy-Source" , ".*?(\\/|%2F).*?" ) . HandlerFunc ( api . CopyObjectHandler )
2016-10-05 15:48:07 -04:00
case "PutBucketPolicy" :
2016-11-07 19:02:27 -05:00
// Register PutBucket Policy handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPut ) . HandlerFunc ( api . PutBucketPolicyHandler ) . Queries ( "policy" , "" )
2016-10-05 15:48:07 -04:00
case "DeleteBucketPolicy" :
2016-11-07 19:02:27 -05:00
// Register Delete bucket HTTP policy handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodDelete ) . HandlerFunc ( api . DeleteBucketPolicyHandler ) . Queries ( "policy" , "" )
2016-10-05 15:48:07 -04:00
case "GetBucketPolicy" :
2016-11-07 19:02:27 -05:00
// Register Get Bucket policy HTTP Handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodGet ) . HandlerFunc ( api . GetBucketPolicyHandler ) . Queries ( "policy" , "" )
2020-03-26 00:06:03 -04:00
case "GetBucketLifecycle" :
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodGet ) . HandlerFunc ( api . GetBucketLifecycleHandler ) . Queries ( "lifecycle" , "" )
2020-03-26 00:06:03 -04:00
case "PutBucketLifecycle" :
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPut ) . HandlerFunc ( api . PutBucketLifecycleHandler ) . Queries ( "lifecycle" , "" )
2020-03-26 00:06:03 -04:00
case "DeleteBucketLifecycle" :
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodDelete ) . HandlerFunc ( api . DeleteBucketLifecycleHandler ) . Queries ( "lifecycle" , "" )
2016-10-05 15:48:07 -04:00
case "GetBucketLocation" :
2016-11-07 19:02:27 -05:00
// Register GetBucketLocation handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodGet ) . HandlerFunc ( api . GetBucketLocationHandler ) . Queries ( "location" , "" )
2016-10-05 15:48:07 -04:00
case "HeadBucket" :
2016-11-07 19:02:27 -05:00
// Register HeadBucket handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodHead ) . HandlerFunc ( api . HeadBucketHandler )
2016-11-16 12:46:09 -05:00
case "DeleteMultipleObjects" :
// Register DeleteMultipleObjects handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPost ) . HandlerFunc ( api . DeleteMultipleObjectsHandler ) . Queries ( "delete" , "" )
2016-10-05 15:48:07 -04:00
case "NewMultipart" :
2016-11-07 19:02:27 -05:00
// Register New Multipart upload handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPost ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . NewMultipartUploadHandler ) . Queries ( "uploads" , "" )
2017-01-31 12:38:34 -05:00
case "CopyObjectPart" :
// Register CopyObjectPart handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPut ) . Path ( "/{object:.+}" ) . HeadersRegexp ( "X-Amz-Copy-Source" , ".*?(\\/|%2F).*?" ) . HandlerFunc ( api . CopyObjectPartHandler ) . Queries ( "partNumber" , "{partNumber:[0-9]+}" , "uploadId" , "{uploadId:.*}" )
2016-10-05 15:48:07 -04:00
case "PutObjectPart" :
2016-11-07 19:02:27 -05:00
// Register PutObjectPart handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPut ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . PutObjectPartHandler ) . Queries ( "partNumber" , "{partNumber:[0-9]+}" , "uploadId" , "{uploadId:.*}" )
2016-10-05 15:48:07 -04:00
case "ListObjectParts" :
2016-11-07 19:02:27 -05:00
// Register ListObjectParts handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodGet ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . ListObjectPartsHandler ) . Queries ( "uploadId" , "{uploadId:.*}" )
2016-10-05 15:48:07 -04:00
case "ListMultipartUploads" :
2016-11-07 19:02:27 -05:00
// Register ListMultipartUploads handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodGet ) . HandlerFunc ( api . ListMultipartUploadsHandler ) . Queries ( "uploads" , "" )
2016-10-05 15:48:07 -04:00
case "CompleteMultipart" :
2016-11-07 19:02:27 -05:00
// Register Complete Multipart Upload handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPost ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . CompleteMultipartUploadHandler ) . Queries ( "uploadId" , "{uploadId:.*}" )
2016-11-08 19:25:00 -05:00
case "AbortMultipart" :
// Register AbortMultipart Handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodDelete ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . AbortMultipartUploadHandler ) . Queries ( "uploadId" , "{uploadId:.*}" )
2016-10-05 15:48:07 -04:00
case "GetBucketNotification" :
2016-11-07 19:02:27 -05:00
// Register GetBucketNotification Handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodGet ) . HandlerFunc ( api . GetBucketNotificationHandler ) . Queries ( "notification" , "" )
2016-10-05 15:48:07 -04:00
case "PutBucketNotification" :
2016-11-07 19:02:27 -05:00
// Register PutBucketNotification Handler.
2020-07-20 15:52:49 -04:00
bucket . Methods ( http . MethodPut ) . HandlerFunc ( api . PutBucketNotificationHandler ) . Queries ( "notification" , "" )
case "ListenNotification" :
// Register ListenNotification Handler.
bucket . Methods ( http . MethodGet ) . HandlerFunc ( api . ListenNotificationHandler ) . Queries ( "events" , "{events:.*}" )
2016-10-05 15:48:07 -04:00
}
2016-10-01 11:23:26 -04:00
}
}
2016-10-05 15:48:07 -04:00
// registerAPIFunctions helper function to add API functions identified by name to the routers.
2018-04-21 22:23:54 -04:00
func registerAPIFunctions ( muxRouter * mux . Router , objLayer ObjectLayer , apiFunctions ... string ) {
2016-10-05 15:48:07 -04:00
if len ( apiFunctions ) == 0 {
// Register all api endpoints by default.
2020-09-15 16:57:15 -04:00
registerAPIRouter ( muxRouter )
2016-10-05 15:48:07 -04:00
return
2016-10-01 11:23:26 -04:00
}
// API Router.
2019-08-06 15:08:58 -04:00
apiRouter := muxRouter . PathPrefix ( SlashSeparator ) . Subrouter ( )
2016-10-01 11:23:26 -04:00
// Bucket router.
2016-10-05 15:48:07 -04:00
bucketRouter := apiRouter . PathPrefix ( "/{bucket}" ) . Subrouter ( )
2016-10-01 11:23:26 -04:00
2016-07-02 22:05:16 -04:00
// All object storage operations are registered as HTTP handlers on `objectAPIHandlers`.
2018-03-28 17:14:06 -04:00
// When the handlers get a HTTP request they use the underlying ObjectLayer to perform operations.
2016-10-10 02:03:10 -04:00
globalObjLayerMutex . Lock ( )
2016-09-23 02:47:48 -04:00
globalObjectAPI = objLayer
2016-10-10 02:03:10 -04:00
globalObjLayerMutex . Unlock ( )
2016-09-23 02:47:48 -04:00
2018-03-28 17:14:06 -04:00
// When cache is enabled, Put and Get operations are passed
// to underlying cache layer to manage object layer operation and disk caching
// operation
2016-07-02 22:05:16 -04:00
api := objectAPIHandlers {
2019-10-31 02:39:09 -04:00
ObjectAPI : func ( ) ObjectLayer {
2020-10-09 12:59:52 -04:00
return globalObjectAPI
2019-10-31 02:39:09 -04:00
} ,
CacheAPI : func ( ) CacheObjectLayer {
2020-10-09 12:59:52 -04:00
return globalCacheObjectAPI
2019-10-31 02:39:09 -04:00
} ,
2016-07-02 22:05:16 -04:00
}
2016-09-23 02:47:48 -04:00
2016-10-05 15:48:07 -04:00
// Register ListBuckets handler.
2020-07-20 15:52:49 -04:00
apiRouter . Methods ( http . MethodGet ) . HandlerFunc ( api . ListBucketsHandler )
2016-10-05 15:48:07 -04:00
// Register all bucket level handlers.
registerBucketLevelFunc ( bucketRouter , api , apiFunctions ... )
}
2020-06-12 23:04:01 -04:00
// Takes in Erasure object layer, and the list of API end points to be tested/required, registers the API end points and returns the HTTP handler.
2016-10-05 15:48:07 -04:00
// Need isolated registration of API end points while writing unit tests for end points.
// All the API end points are registered only for the default case.
func initTestAPIEndPoints ( objLayer ObjectLayer , apiFunctions [ ] string ) http . Handler {
// initialize a new mux router.
// goriilla/mux is the library used to register all the routes and handle them.
2021-08-08 01:43:01 -04:00
muxRouter := mux . NewRouter ( ) . SkipClean ( true ) . UseEncodedPath ( )
2016-10-05 15:48:07 -04:00
if len ( apiFunctions ) > 0 {
// Iterate the list of API functions requested for and register them in mux HTTP handler.
registerAPIFunctions ( muxRouter , objLayer , apiFunctions ... )
2021-08-08 01:43:01 -04:00
muxRouter . Use ( globalHandlers ... )
2016-10-05 15:48:07 -04:00
return muxRouter
2016-07-02 22:05:16 -04:00
}
2020-09-15 16:57:15 -04:00
registerAPIRouter ( muxRouter )
2021-08-08 01:43:01 -04:00
muxRouter . Use ( globalHandlers ... )
2016-07-02 22:05:16 -04:00
return muxRouter
2016-08-15 19:13:03 -04:00
}
2016-11-07 14:43:35 -05:00
2016-11-11 10:18:44 -05:00
// generateTLSCertKey creates valid key/cert with registered DNS or IP address
// depending on the passed parameter. That way, we can use tls config without
// passing InsecureSkipVerify flag. This code is a simplified version of
// https://golang.org/src/crypto/tls/generate_cert.go
func generateTLSCertKey ( host string ) ( [ ] byte , [ ] byte , error ) {
validFor := 365 * 24 * time . Hour
rsaBits := 2048
if len ( host ) == 0 {
return nil , nil , fmt . Errorf ( "Missing host parameter" )
}
publicKey := func ( priv interface { } ) interface { } {
switch k := priv . ( type ) {
case * rsa . PrivateKey :
return & k . PublicKey
case * ecdsa . PrivateKey :
return & k . PublicKey
default :
return nil
}
}
pemBlockForKey := func ( priv interface { } ) * pem . Block {
switch k := priv . ( type ) {
case * rsa . PrivateKey :
return & pem . Block { Type : "RSA PRIVATE KEY" , Bytes : x509 . MarshalPKCS1PrivateKey ( k ) }
case * ecdsa . PrivateKey :
b , err := x509 . MarshalECPrivateKey ( k )
if err != nil {
fmt . Fprintf ( os . Stderr , "Unable to marshal ECDSA private key: %v" , err )
os . Exit ( 2 )
}
return & pem . Block { Type : "EC PRIVATE KEY" , Bytes : b }
default :
return nil
}
}
var priv interface { }
var err error
priv , err = rsa . GenerateKey ( crand . Reader , rsaBits )
if err != nil {
2019-12-02 12:28:01 -05:00
return nil , nil , fmt . Errorf ( "failed to generate private key: %w" , err )
2016-11-11 10:18:44 -05:00
}
notBefore := time . Now ( )
notAfter := notBefore . Add ( validFor )
serialNumberLimit := new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 128 )
serialNumber , err := crand . Int ( crand . Reader , serialNumberLimit )
if err != nil {
2019-12-02 12:28:01 -05:00
return nil , nil , fmt . Errorf ( "failed to generate serial number: %w" , err )
2016-11-11 10:18:44 -05:00
}
template := x509 . Certificate {
SerialNumber : serialNumber ,
Subject : pkix . Name {
Organization : [ ] string { "Acme Co" } ,
} ,
NotBefore : notBefore ,
NotAfter : notAfter ,
KeyUsage : x509 . KeyUsageKeyEncipherment | x509 . KeyUsageDigitalSignature ,
ExtKeyUsage : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageServerAuth } ,
BasicConstraintsValid : true ,
}
hosts := strings . Split ( host , "," )
for _ , h := range hosts {
if ip := net . ParseIP ( h ) ; ip != nil {
template . IPAddresses = append ( template . IPAddresses , ip )
} else {
template . DNSNames = append ( template . DNSNames , h )
}
}
template . IsCA = true
template . KeyUsage |= x509 . KeyUsageCertSign
derBytes , err := x509 . CreateCertificate ( crand . Reader , & template , & template , publicKey ( priv ) , priv )
if err != nil {
2019-12-02 12:28:01 -05:00
return nil , nil , fmt . Errorf ( "Failed to create certificate: %w" , err )
2016-11-11 10:18:44 -05:00
}
certOut := bytes . NewBuffer ( [ ] byte { } )
pem . Encode ( certOut , & pem . Block { Type : "CERTIFICATE" , Bytes : derBytes } )
keyOut := bytes . NewBuffer ( [ ] byte { } )
pem . Encode ( keyOut , pemBlockForKey ( priv ) )
return certOut . Bytes ( ) , keyOut . Bytes ( ) , nil
}
2017-04-11 18:44:27 -04:00
2021-01-26 23:47:42 -05:00
func mustGetPoolEndpoints ( args ... string ) EndpointServerPools {
2019-11-19 20:42:27 -05:00
endpoints := mustGetNewEndpoints ( args ... )
2020-11-05 14:48:55 -05:00
drivesPerSet := len ( args )
setCount := 1
if len ( args ) >= 16 {
drivesPerSet = 16
setCount = len ( args ) / 16
}
2021-01-26 23:47:42 -05:00
return [ ] PoolEndpoints { {
2020-11-05 14:48:55 -05:00
SetCount : setCount ,
DrivesPerSet : drivesPerSet ,
2019-11-19 20:42:27 -05:00
Endpoints : endpoints ,
} }
}
func mustGetNewEndpoints ( args ... string ) ( endpoints Endpoints ) {
endpoints , err := NewEndpoints ( args ... )
logger . FatalIf ( err , "unable to create new endpoint list" )
2017-04-11 18:44:27 -04:00
return endpoints
}
2020-12-01 16:50:33 -05:00
func getEndpointsLocalAddr ( endpointServerPools EndpointServerPools ) string {
for _ , endpoints := range endpointServerPools {
2019-11-19 20:42:27 -05:00
for _ , endpoint := range endpoints . Endpoints {
if endpoint . IsLocal && endpoint . Type ( ) == URLEndpointType {
return endpoint . Host
}
2017-04-11 18:44:27 -04:00
}
}
2018-12-14 02:37:46 -05:00
return net . JoinHostPort ( globalMinioHost , globalMinioPort )
2017-04-11 18:44:27 -04:00
}
2017-05-03 02:54:22 -04:00
// fetches a random number between range min-max.
func getRandomRange ( min , max int , seed int64 ) int {
// special value -1 means no explicit seeding.
if seed != - 1 {
rand . Seed ( seed )
}
return rand . Intn ( max - min ) + min
}
// Randomizes the order of bytes in the byte array
// using Knuth Fisher-Yates shuffle algorithm.
func randomizeBytes ( s [ ] byte , seed int64 ) [ ] byte {
// special value -1 means no explicit seeding.
if seed != - 1 {
rand . Seed ( seed )
}
n := len ( s )
var j int
for i := 0 ; i < n - 1 ; i ++ {
j = i + rand . Intn ( n - i )
s [ i ] , s [ j ] = s [ j ] , s [ i ]
}
return s
}
2017-10-09 19:41:35 -04:00
func TestToErrIsNil ( t * testing . T ) {
if toObjectErr ( nil ) != nil {
t . Errorf ( "Test expected to return nil, failed instead got a non-nil value %s" , toObjectErr ( nil ) )
}
if toStorageErr ( nil ) != nil {
t . Errorf ( "Test expected to return nil, failed instead got a non-nil value %s" , toStorageErr ( nil ) )
}
2018-11-12 14:07:43 -05:00
ctx := context . Background ( )
2019-02-12 04:25:52 -05:00
if toAPIError ( ctx , nil ) != noError {
t . Errorf ( "Test expected error code to be ErrNone, failed instead provided %s" , toAPIError ( ctx , nil ) . Code )
2017-10-09 19:41:35 -04:00
}
}
2018-09-20 22:22:09 -04:00
// Uploads an object using DummyDataGen directly via the http
// handler. Each part in a multipart object is a new DummyDataGen
// instance (so the part sizes are needed to reconstruct the whole
// object). When `len(partSizes) == 1`, asMultipart is used to upload
// the object as multipart with 1 part or as a regular single object.
//
// All upload failures are considered test errors - this function is
// intended as a helper for other tests.
func uploadTestObject ( t * testing . T , apiRouter http . Handler , creds auth . Credentials , bucketName , objectName string ,
2022-01-02 12:15:06 -05:00
partSizes [ ] int64 , metadata map [ string ] string , asMultipart bool ,
) {
2018-09-20 22:22:09 -04:00
if len ( partSizes ) == 0 {
t . Fatalf ( "Cannot upload an object without part sizes" )
}
if len ( partSizes ) > 1 {
asMultipart = true
}
checkRespErr := func ( rec * httptest . ResponseRecorder , exp int ) {
if rec . Code != exp {
b , err := ioutil . ReadAll ( rec . Body )
t . Fatalf ( "Expected: %v, Got: %v, Body: %s, err: %v" , exp , rec . Code , string ( b ) , err )
}
}
if ! asMultipart {
srcData := NewDummyDataGen ( partSizes [ 0 ] , 0 )
2020-07-20 15:52:49 -04:00
req , err := newTestSignedRequestV4 ( http . MethodPut , getPutObjectURL ( "" , bucketName , objectName ) ,
2018-09-20 22:22:09 -04:00
partSizes [ 0 ] , srcData , creds . AccessKey , creds . SecretKey , metadata )
if err != nil {
t . Fatalf ( "Unexpected err: %#v" , err )
}
rec := httptest . NewRecorder ( )
apiRouter . ServeHTTP ( rec , req )
checkRespErr ( rec , http . StatusOK )
} else {
// Multipart upload - each part is a new DummyDataGen
// (so the part lengths are required to verify the
// object when reading).
// Initiate mp upload
2020-07-20 15:52:49 -04:00
reqI , err := newTestSignedRequestV4 ( http . MethodPost , getNewMultipartURL ( "" , bucketName , objectName ) ,
2018-09-20 22:22:09 -04:00
0 , nil , creds . AccessKey , creds . SecretKey , metadata )
if err != nil {
t . Fatalf ( "Unexpected err: %#v" , err )
}
rec := httptest . NewRecorder ( )
apiRouter . ServeHTTP ( rec , reqI )
checkRespErr ( rec , http . StatusOK )
decoder := xml . NewDecoder ( rec . Body )
multipartResponse := & InitiateMultipartUploadResponse { }
err = decoder . Decode ( multipartResponse )
if err != nil {
t . Fatalf ( "Error decoding the recorded response Body" )
}
upID := multipartResponse . UploadID
// Upload each part
var cp [ ] CompletePart
cumulativeSum := int64 ( 0 )
for i , partLen := range partSizes {
partID := i + 1
partSrc := NewDummyDataGen ( partLen , cumulativeSum )
cumulativeSum += partLen
2020-07-20 15:52:49 -04:00
req , errP := newTestSignedRequestV4 ( http . MethodPut ,
2018-09-20 22:22:09 -04:00
getPutObjectPartURL ( "" , bucketName , objectName , upID , fmt . Sprintf ( "%d" , partID ) ) ,
partLen , partSrc , creds . AccessKey , creds . SecretKey , metadata )
if errP != nil {
t . Fatalf ( "Unexpected err: %#v" , errP )
}
rec = httptest . NewRecorder ( )
apiRouter . ServeHTTP ( rec , req )
checkRespErr ( rec , http . StatusOK )
2020-05-18 12:59:45 -04:00
header := rec . Header ( )
if v , ok := header [ "ETag" ] ; ok {
etag := v [ 0 ]
if etag == "" {
t . Fatalf ( "Unexpected empty etag" )
}
cp = append ( cp , CompletePart { partID , etag [ 1 : len ( etag ) - 1 ] } )
} else {
t . Fatalf ( "Missing etag header" )
2018-09-20 22:22:09 -04:00
}
}
// Call CompleteMultipart API
compMpBody , err := xml . Marshal ( CompleteMultipartUpload { Parts : cp } )
if err != nil {
t . Fatalf ( "Unexpected err: %#v" , err )
}
2020-07-20 15:52:49 -04:00
reqC , errP := newTestSignedRequestV4 ( http . MethodPost ,
2018-09-20 22:22:09 -04:00
getCompleteMultipartUploadURL ( "" , bucketName , objectName , upID ) ,
int64 ( len ( compMpBody ) ) , bytes . NewReader ( compMpBody ) ,
creds . AccessKey , creds . SecretKey , metadata )
if errP != nil {
t . Fatalf ( "Unexpected err: %#v" , errP )
}
rec = httptest . NewRecorder ( )
apiRouter . ServeHTTP ( rec , reqC )
checkRespErr ( rec , http . StatusOK )
}
}