2016-05-06 20:57:04 +02:00
/ *
2019-04-09 20:39:42 +02:00
* MinIO Cloud Storage , ( C ) 2015 , 2016 , 2017 , 2018 MinIO , Inc .
2016-05-06 20:57:04 +02:00
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2016-08-19 01:23:42 +02:00
package cmd
2016-05-06 20:57:04 +02:00
import (
2016-10-10 10:42:32 +02:00
"bufio"
2016-06-21 21:10:18 +02:00
"bytes"
2018-03-15 21:27:16 +01:00
"context"
2016-11-11 16:18:44 +01:00
"crypto/ecdsa"
2017-09-26 20:00:07 +02:00
"crypto/hmac"
2016-11-11 16:18:44 +01:00
crand "crypto/rand"
"crypto/rsa"
2017-09-26 20:00:07 +02:00
"crypto/sha1"
2016-10-26 11:30:31 +02:00
"crypto/tls"
2016-11-11 16:18:44 +01:00
"crypto/x509"
"crypto/x509/pkix"
2017-09-26 20:00:07 +02:00
"encoding/base64"
2016-06-21 21:10:18 +02:00
"encoding/hex"
2016-08-16 01:13:03 +02:00
"encoding/json"
2016-11-11 16:18:44 +01:00
"encoding/pem"
2018-09-21 04:22:09 +02:00
"encoding/xml"
2016-08-16 01:13:03 +02:00
"errors"
2016-06-21 21:10:18 +02:00
"fmt"
"io"
2016-05-06 20:57:04 +02:00
"io/ioutil"
2016-11-11 16:18:44 +01:00
"math/big"
2016-06-26 04:07:44 +02:00
"math/rand"
2016-10-13 18:19:04 +02:00
"net"
2016-06-21 21:10:18 +02:00
"net/http"
"net/http/httptest"
2016-06-26 04:07:44 +02:00
"net/url"
2016-05-06 20:57:04 +02:00
"os"
2016-09-16 22:06:49 +02:00
"reflect"
2016-06-21 21:10:18 +02:00
"sort"
2016-06-29 12:13:44 +02:00
"strconv"
2016-06-21 21:10:18 +02:00
"strings"
2016-06-29 12:13:44 +02:00
"sync"
2016-05-06 20:57:04 +02:00
"testing"
2016-06-21 21:10:18 +02:00
"time"
2016-07-03 04:05:16 +02:00
2016-10-05 21:48:07 +02:00
"github.com/fatih/color"
2018-04-22 04:23:54 +02:00
"github.com/gorilla/mux"
2019-05-30 01:35:12 +02:00
"github.com/minio/minio-go/v6/pkg/s3signer"
"github.com/minio/minio-go/v6/pkg/s3utils"
2019-10-23 07:59:13 +02:00
"github.com/minio/minio/cmd/config"
2018-04-06 00:04:40 +02:00
"github.com/minio/minio/cmd/logger"
2017-10-31 19:54:32 +01:00
"github.com/minio/minio/pkg/auth"
2020-01-27 23:12:34 +01:00
"github.com/minio/minio/pkg/bucket/policy"
2017-10-22 07:30:34 +02:00
"github.com/minio/minio/pkg/hash"
2016-05-06 20:57:04 +02:00
)
2016-07-08 04:50:44 +02:00
// Tests should initNSLock only once.
func init ( ) {
2016-12-23 16:12:19 +01:00
// Set as non-distributed.
globalIsDistXL = false
2016-10-05 21:48:07 +02:00
// Disable printing console messages during tests.
color . Output = ioutil . Discard
2016-12-09 05:35:07 +01:00
2017-03-02 19:34:37 +01:00
// Set system resources to maximum.
setMaxResources ( )
2018-02-16 02:45:57 +01:00
2019-12-17 05:30:57 +01:00
// Initialize globalConsoleSys system
globalConsoleSys = NewConsoleLogger ( context . Background ( ) )
2019-10-16 04:39:14 +02:00
logger . Disable = true
2019-12-05 00:32:37 +01:00
initHelp ( )
2019-01-17 13:58:18 +01:00
// Uncomment the following line to see trace logs during unit tests.
// logger.AddTarget(console.New())
2016-07-08 04:50:44 +02:00
}
2017-10-10 01:41:35 +02:00
// concurreny level for certain parallel tests.
2017-10-10 11:14:42 +02:00
const testConcurrencyLevel = 10
2017-10-10 01:41:35 +02: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.
///
/// Content-Length:
///
/// This is ignored from signing because generating a pre-signed URL should not provide a content-length
/// constraint, specifically when vending a S3 pre-signed PUT URL. The corollary to this is that when
/// sending regular requests (non-pre-signed), the signature contains a checksum of the body, which
/// implicitly validates the payload length (since changing the number of bytes would change the checksum)
/// and therefore this header is not valuable in the signature.
///
/// Content-Type:
///
/// Signing this header causes quite a number of problems in browser environments, where browsers
/// like to modify and normalize the content-type header in different ways. There is more information
/// on this in https://github.com/aws/aws-sdk-js/issues/244. Avoiding this field simplifies logic
/// and reduces the possibility of future bugs
///
/// Authorization:
///
/// Is skipped for obvious reasons
///
var ignoredHeaders = map [ string ] bool {
"Authorization" : true ,
"Content-Type" : true ,
"Content-Length" : true ,
"User-Agent" : true ,
}
// 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-15 02:36:41 +01:00
func mustGetPutObjReader ( t TestErrHandler , data io . Reader , size int64 , md5hex , sha256hex string ) * PutObjReader {
2019-05-09 03:35:40 +02:00
hr , err := hash . NewReader ( data , size , md5hex , sha256hex , size , globalCLIContext . StrictS3Compat )
2017-10-22 07:30:34 +02:00
if err != nil {
t . Fatal ( err )
}
2018-11-15 02:36:41 +01:00
return NewPutObjReader ( hr , nil , nil )
2017-10-22 07:30:34 +02:00
}
2017-10-10 01:41:35 +02:00
// calculateSignedChunkLength - calculates the length of the overall stream (data + metadata)
func calculateStreamContentLength ( dataLen , chunkSize int64 ) int64 {
if dataLen <= 0 {
return 0
}
chunksCount := int64 ( dataLen / chunkSize )
remainingBytes := int64 ( dataLen % chunkSize )
var streamLen int64
streamLen += chunksCount * calculateSignedChunkLength ( chunkSize )
if remainingBytes > 0 {
streamLen += calculateSignedChunkLength ( remainingBytes )
}
streamLen += calculateSignedChunkLength ( 0 )
return streamLen
}
2016-08-31 04:22:27 +02:00
func prepareFS ( ) ( ObjectLayer , string , error ) {
2017-01-17 02:05:00 +01:00
nDisks := 1
fsDirs , err := getRandomDisks ( nDisks )
2016-08-31 04:22:27 +02:00
if err != nil {
return nil , "" , err
}
2018-02-20 21:21:12 +01:00
obj , err := NewFSObjectLayer ( fsDirs [ 0 ] )
2017-01-17 02:05:00 +01:00
if err != nil {
return nil , "" , err
}
2017-03-01 03:05:52 +01:00
return obj , fsDirs [ 0 ] , nil
2016-08-31 04:22:27 +02:00
}
2019-05-22 22:36:16 +02:00
func prepareXLSets32 ( ) ( ObjectLayer , [ ] string , error ) {
2018-02-16 02:45:57 +01:00
fsDirs1 , err := getRandomDisks ( 16 )
if err != nil {
return nil , nil , err
}
2019-11-20 02:42:27 +01:00
endpoints1 := mustGetNewEndpoints ( fsDirs1 ... )
2018-02-16 02:45:57 +01:00
fsDirs2 , err := getRandomDisks ( 16 )
if err != nil {
removeRoots ( fsDirs1 )
return nil , nil , err
}
2019-11-20 02:42:27 +01:00
endpoints2 := mustGetNewEndpoints ( fsDirs2 ... )
2018-02-16 02:45:57 +01:00
endpoints := append ( endpoints1 , endpoints2 ... )
fsDirs := append ( fsDirs1 , fsDirs2 ... )
2020-01-18 00:39:07 +01:00
format , err := waitForFormatXL ( true , endpoints , 1 , 2 , 16 , "" )
2018-02-16 02:45:57 +01:00
if err != nil {
removeRoots ( fsDirs )
return nil , nil , err
}
objAPI , err := newXLSets ( endpoints , format , 2 , 16 )
if err != nil {
return nil , nil , err
}
return objAPI , fsDirs , nil
}
2017-12-28 18:32:48 +01:00
func prepareXL ( nDisks int ) ( ObjectLayer , [ ] string , error ) {
2016-08-31 04:22:27 +02:00
fsDirs , err := getRandomDisks ( nDisks )
if err != nil {
return nil , nil , err
}
2019-11-20 02:42:27 +01:00
obj , _ , err := initObjectLayer ( mustGetZoneEndpoints ( fsDirs ... ) )
2016-08-31 04:22:27 +02:00
if err != nil {
removeRoots ( fsDirs )
return nil , nil , err
}
return obj , fsDirs , nil
}
2017-12-28 18:32:48 +01:00
func prepareXL16 ( ) ( ObjectLayer , [ ] string , error ) {
return prepareXL ( 16 )
}
2017-01-17 02:05:00 +01:00
// Initialize FS objects.
func initFSObjects ( disk string , t * testing . T ) ( obj ObjectLayer ) {
var err error
2018-02-20 21:21:12 +01:00
obj , err = NewFSObjectLayer ( disk )
2017-01-17 02:05:00 +01:00
if err != nil {
t . Fatal ( err )
}
2018-08-15 06:41:47 +02:00
newTestConfig ( globalMinioDefaultRegion , obj )
2017-01-17 02:05:00 +01:00
return obj
}
2016-07-26 05:39:14 +02:00
// TestErrHandler - Golang Testing.T and Testing.B, and gocheck.C satisfy this interface.
2016-06-21 21:10:18 +02:00
// This makes it easy to run the TestServer from any of the tests.
2016-07-26 05:39:14 +02: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 21:10:18 +02:00
type TestErrHandler interface {
Error ( args ... interface { } )
Errorf ( format string , args ... interface { } )
Failed ( ) bool
Fatal ( args ... interface { } )
Fatalf ( format string , args ... interface { } )
}
2016-05-06 20:57:04 +02:00
const (
2016-11-01 18:21:16 +01:00
// FSTestStr is the string which is used as notation for Single node ObjectLayer in the unit tests.
FSTestStr string = "FS"
2017-01-17 02:05:00 +01:00
2016-11-01 18:21:16 +01:00
// XLTestStr is the string which is used as notation for XL ObjectLayer in the unit tests.
XLTestStr string = "XL"
2018-02-16 02:45:57 +01:00
// XLSetsTestStr is the string which is used as notation for XL sets object layer in the unit tests.
XLSetsTestStr string = "XLSet"
2016-05-06 20:57:04 +02:00
)
2016-06-26 04:07:44 +02: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 12:13:44 +02:00
// Random number state.
// We generate random temporary file names so that there's a good
// chance the file doesn't exist yet.
var randN uint32
var randmu sync . Mutex
2016-12-16 07:25:05 +01:00
// Temp files created in default Tmp dir
var globalTestTmpDir = os . TempDir ( )
2016-07-02 10:59:28 +02:00
// reseed - returns a new seed every time the function is called.
2016-06-29 12:13:44 +02:00
func reseed ( ) uint32 {
return uint32 ( time . Now ( ) . UnixNano ( ) + int64 ( os . Getpid ( ) ) )
}
2016-07-02 10:59:28 +02:00
// nextSuffix - provides a new unique suffix every time the function is called.
2016-06-29 12:13:44 +02: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 22:06:49 +02: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 20:39:42 +02:00
// TestServer encapsulates an instantiation of a MinIO instance with a temporary backend.
2016-06-21 21:10:18 +02:00
// Example usage:
// s := StartTestServer(t,"XL")
// defer s.Stop()
type TestServer struct {
Root string
2019-11-20 02:42:27 +01:00
Disks EndpointZones
2016-06-21 21:10:18 +02:00
AccessKey string
SecretKey string
Server * httptest . Server
2016-08-31 04:22:27 +02:00
Obj ObjectLayer
2016-06-21 21:10:18 +02:00
}
2017-03-01 03:05:52 +01:00
// UnstartedTestServer - Configures a temp FS/XL backend,
// initializes the endpoints and configures the test server.
// The server should be started using the Start() method.
2016-10-26 11:30:31 +02:00
func UnstartedTestServer ( t TestErrHandler , instanceType string ) TestServer {
2016-06-21 21:10:18 +02:00
// create an instance of TestServer.
testServer := TestServer { }
2017-03-01 03:05:52 +01:00
// return FS/XL object layer and temp backend.
objLayer , disks , err := prepareTestBackend ( instanceType )
2016-06-21 21:10:18 +02:00
if err != nil {
2017-03-01 03:05:52 +01:00
t . Fatal ( err )
2016-06-21 21:10:18 +02:00
}
2018-08-15 06:41:47 +02:00
2017-03-01 03:05:52 +01:00
// set the server configuration.
2018-08-15 06:41:47 +02:00
if err = newTestConfig ( globalMinioDefaultRegion , objLayer ) ; err != nil {
2016-07-03 04:05:16 +02:00
t . Fatalf ( "%s" , err )
2016-06-21 21:10:18 +02:00
}
2016-07-26 09:01:35 +02:00
2016-08-31 04:22:27 +02:00
// Test Server needs to start before formatting of disks.
2016-07-26 09:01:35 +02:00
// Get credential.
2019-10-23 07:59:13 +02:00
credentials := globalActiveCred
2016-07-26 09:01:35 +02:00
2017-03-01 03:05:52 +01:00
testServer . Obj = objLayer
2019-11-20 02:42:27 +01:00
testServer . Disks = mustGetZoneEndpoints ( disks ... )
2016-12-26 19:21:23 +01:00
testServer . AccessKey = credentials . AccessKey
testServer . SecretKey = credentials . SecretKey
2016-07-03 04:05:16 +02:00
2017-04-12 00:44:27 +02:00
httpHandler , err := configureServerHandler ( testServer . Disks )
2016-10-13 08:13:24 +02:00
if err != nil {
t . Fatalf ( "Failed to configure one of the RPC services <ERROR> %s" , err )
}
2016-10-13 18:19:04 +02:00
// Run TestServer.
2016-10-26 11:30:31 +02:00
testServer . Server = httptest . NewUnstartedServer ( httpHandler )
2016-10-13 18:19:04 +02:00
2016-10-10 08:03:10 +02:00
globalObjLayerMutex . Lock ( )
2016-08-31 04:22:27 +02:00
globalObjectAPI = objLayer
2016-10-10 08:03:10 +02:00
globalObjLayerMutex . Unlock ( )
2016-10-13 18:19:04 +02:00
// initialize peer rpc
2017-04-12 00:44:27 +02:00
host , port := mustSplitHostPort ( testServer . Server . Listener . Addr ( ) . String ( ) )
2016-10-27 12:30:52 +02:00
globalMinioHost = host
globalMinioPort = port
2017-04-12 00:44:27 +02:00
globalMinioAddr = getEndpointsLocalAddr ( testServer . Disks )
2018-07-10 03:50:31 +02:00
2018-10-09 23:00:01 +02:00
globalConfigSys = NewConfigSys ( )
globalIAMSys = NewIAMSys ( )
globalIAMSys . Init ( objLayer )
2016-10-13 18:19:04 +02:00
2019-10-02 02:05:02 +02:00
buckets , err := objLayer . ListBuckets ( context . Background ( ) )
if err != nil {
t . Fatalf ( "Unable to list buckets on backend %s" , err )
}
2018-04-25 00:53:30 +02:00
globalPolicySys = NewPolicySys ( )
2019-10-02 02:05:02 +02:00
globalPolicySys . Init ( buckets , objLayer )
2018-10-09 23:00:01 +02:00
2019-11-09 18:27:23 +01:00
globalNotificationSys = NewNotificationSys ( testServer . Disks )
2019-10-02 02:05:02 +02:00
globalNotificationSys . Init ( buckets , objLayer )
2019-07-19 22:20:33 +02:00
globalLifecycleSys = NewLifecycleSys ( )
2019-10-02 02:05:02 +02:00
globalLifecycleSys . Init ( buckets , objLayer )
2018-04-25 00:53:30 +02:00
2016-07-03 04:05:16 +02:00
return testServer
2016-10-26 11:30:31 +02:00
}
2016-11-11 16:18:44 +01: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 11:30:31 +02: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 16:18:44 +01:00
cer , err := tls . X509KeyPair ( cert , key )
2016-10-26 11:30:31 +02: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-03 04:05:16 +02:00
}
2016-12-13 20:51:48 +01:00
// Sets the global config path to empty string.
func resetGlobalConfigPath ( ) {
2019-01-02 19:05:16 +01:00
globalConfigDir = & ConfigDir { path : "" }
2016-12-13 20:51:48 +01:00
}
2019-03-04 23:33:14 +01:00
func resetGlobalServiceDoneCh ( ) {
2019-03-05 17:04:17 +01:00
// Repeatedly send on the service done channel, so that
// listening go-routines will quit. This works better than
// closing the channel - closing introduces a new race, as the
// current thread writes to the variable, and other threads
// listening on it, read from it.
loop :
for {
select {
case GlobalServiceDoneCh <- struct { } { } :
default :
break loop
}
}
2019-03-04 23:33:14 +01:00
}
2016-12-13 20:51:48 +01: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 22:12:47 +01:00
globalServerConfigMu . Lock ( )
2016-12-13 20:51:48 +01:00
// Save the loaded config globally.
2017-11-29 22:12:47 +01:00
globalServerConfig = nil
globalServerConfigMu . Unlock ( )
2016-12-13 20:51:48 +01:00
}
2017-01-23 09:32:55 +01:00
func resetGlobalEndpoints ( ) {
2019-11-20 02:42:27 +01:00
globalEndpoints = EndpointZones { }
2017-01-23 09:32:55 +01:00
}
func resetGlobalIsXL ( ) {
globalIsXL = false
}
2018-01-22 23:54:55 +01:00
// reset global heal state
func resetGlobalHealState ( ) {
2020-01-16 03:30:32 +01:00
// Init global heal state
2019-06-09 07:14:07 +02:00
if globalAllHealState == nil {
2020-01-16 03:30:32 +01:00
globalAllHealState = initHealState ( )
} else {
globalAllHealState . Lock ( )
for _ , v := range globalAllHealState . healSeqMap {
if ! v . hasEnded ( ) {
v . stop ( )
}
}
globalAllHealState . Unlock ( )
2019-06-09 07:14:07 +02:00
}
2020-01-16 03:30:32 +01:00
// Init background heal state
if globalBackgroundHealState == nil {
globalBackgroundHealState = initHealState ( )
} else {
globalBackgroundHealState . Lock ( )
for _ , v := range globalBackgroundHealState . healSeqMap {
if ! v . hasEnded ( ) {
v . stop ( )
}
2018-01-22 23:54:55 +01:00
}
2020-01-16 03:30:32 +01:00
globalBackgroundHealState . Unlock ( )
2018-01-22 23:54:55 +01:00
}
}
2018-10-18 02:25:50 +02:00
// sets globalIAMSys to `nil`.
func resetGlobalIAMSys ( ) {
globalIAMSys = nil
}
2016-12-13 20:51:48 +01: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 ( ) {
2019-03-04 23:33:14 +01:00
// close any indefinitely running go-routines from previous
// tests.
resetGlobalServiceDoneCh ( )
2016-12-13 20:51:48 +01:00
// set globalObjectAPI to `nil`.
resetGlobalObjectAPI ( )
// Reset config path set.
resetGlobalConfigPath ( )
// Reset Global server config.
resetGlobalConfig ( )
2017-01-23 09:32:55 +01:00
// Reset global endpoints.
resetGlobalEndpoints ( )
// Reset global isXL flag.
resetGlobalIsXL ( )
2018-01-22 23:54:55 +01:00
// Reset global heal state
resetGlobalHealState ( )
2018-10-18 02:25:50 +02:00
// Reset globalIAMSys to `nil`
resetGlobalIAMSys ( )
2016-12-13 20:51:48 +01:00
}
2016-07-03 04:05:16 +02:00
// Configure the server for the test run.
2018-08-15 06:41:47 +02:00
func newTestConfig ( bucketLocation string , obj ObjectLayer ) ( err error ) {
2016-07-26 09:01:35 +02:00
// Initialize server config.
2018-10-09 23:00:01 +02:00
if err = newSrvConfig ( obj ) ; err != nil {
2018-08-15 06:41:47 +02:00
return err
2016-06-21 21:10:18 +02:00
}
2016-07-26 09:01:35 +02:00
2019-10-23 07:59:13 +02:00
globalActiveCred = auth . Credentials {
AccessKey : auth . DefaultAccessKey ,
SecretKey : auth . DefaultSecretKey ,
}
2019-11-01 23:53:16 +01:00
globalConfigEncrypted = true
2016-07-26 09:01:35 +02:00
// Set a default region.
2019-10-23 07:59:13 +02:00
config . SetRegion ( globalServerConfig , bucketLocation )
2016-07-26 09:01:35 +02:00
// Save config.
2019-11-21 13:24:51 +01:00
return saveServerConfig ( context . Background ( ) , obj , globalServerConfig )
2016-06-21 21:10:18 +02:00
}
// Deleting the temporary backend and stopping the server.
func ( testServer TestServer ) Stop ( ) {
2017-08-13 04:25:43 +02:00
os . RemoveAll ( testServer . Root )
2019-11-20 02:42:27 +01:00
for _ , ep := range testServer . Disks {
for _ , disk := range ep . Endpoints {
os . RemoveAll ( disk . Path )
}
2016-06-21 21:10:18 +02:00
}
testServer . Server . Close ( )
}
2016-10-10 10:42:32 +02: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 22:37:14 +02:00
// Sign given request using Signature V4.
2016-10-12 08:46:51 +02:00
func signStreamingRequest ( req * http . Request , accessKey , secretKey string , currTime time . Time ) ( string , error ) {
2016-09-04 22:37:14 +02:00
// Get hashed payload.
hashedPayload := req . Header . Get ( "x-amz-content-sha256" )
if hashedPayload == "" {
2016-11-16 03:14:23 +01:00
return "" , fmt . Errorf ( "Invalid hashed payload" )
2016-09-04 22:37:14 +02: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.
req . URL . RawQuery = strings . Replace ( req . URL . Query ( ) . Encode ( ) , "+" , "%20" , - 1 )
// Get canonical URI.
2018-06-05 19:48:51 +02:00
canonicalURI := s3utils . EncodePath ( req . URL . Path )
2016-09-04 22:37:14 +02: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 21:24:34 +01:00
globalMinioDefaultRegion ,
2019-02-28 02:46:55 +01:00
string ( serviceS3 ) ,
2016-09-04 22:37:14 +02:00
"aws4_request" ,
2019-08-06 21:08:58 +02:00
} , SlashSeparator )
2016-09-04 22:37:14 +02:00
stringToSign := "AWS4-HMAC-SHA256" + "\n" + currTime . Format ( iso8601Format ) + "\n"
stringToSign = stringToSign + scope + "\n"
2016-11-21 22:51:05 +01:00
stringToSign = stringToSign + getSHA256Hash ( [ ] byte ( canonicalRequest ) )
2016-09-04 22:37:14 +02:00
date := sumHMAC ( [ ] byte ( "AWS4" + secretKey ) , [ ] byte ( currTime . Format ( yyyymmdd ) ) )
2017-01-18 21:24:34 +01:00
region := sumHMAC ( date , [ ] byte ( globalMinioDefaultRegion ) )
2019-02-28 02:46:55 +01:00
service := sumHMAC ( region , [ ] byte ( string ( serviceS3 ) ) )
2016-09-04 22:37:14 +02:00
signingKey := sumHMAC ( service , [ ] byte ( "aws4_request" ) )
signature := hex . EncodeToString ( sumHMAC ( signingKey , [ ] byte ( stringToSign ) ) )
// final Authorization header
parts := [ ] string {
2019-08-06 21:08:58 +02:00
"AWS4-HMAC-SHA256" + " Credential=" + accessKey + SlashSeparator + scope ,
2016-09-04 22:37:14 +02: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 == "" {
method = "POST"
}
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 11:45:42 +02:00
2016-09-04 22:37:14 +02:00
// Add body
req . Body = ioutil . NopCloser ( body )
req . ContentLength = contentLength
return req , nil
}
2016-10-12 08:46:51 +02:00
func assembleStreamingChunks ( req * http . Request , body io . ReadSeeker , chunkSize int64 ,
secretKey , signature string , currTime time . Time ) ( * http . Request , error ) {
2016-09-04 22:37:14 +02:00
2019-10-23 07:59:13 +02:00
regionStr := globalServerRegion
2016-09-04 22:37:14 +02: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 11:45:42 +02:00
regionStr ,
2019-02-28 02:46:55 +01:00
string ( serviceS3 ) ,
2016-09-04 22:37:14 +02:00
"aws4_request" ,
2019-08-06 21:08:58 +02:00
} , SlashSeparator )
2016-09-04 22:37:14 +02: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(""))
2016-11-21 22:51:05 +01:00
stringToSign = stringToSign + getSHA256Hash ( buffer [ : n ] )
2016-09-04 22:37:14 +02:00
date := sumHMAC ( [ ] byte ( "AWS4" + secretKey ) , [ ] byte ( currTime . Format ( yyyymmdd ) ) )
2016-09-16 11:45:42 +02:00
region := sumHMAC ( date , [ ] byte ( regionStr ) )
2019-02-28 02:46:55 +01:00
service := sumHMAC ( region , [ ] byte ( serviceS3 ) )
2016-09-04 22:37:14 +02: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 08:46:51 +02: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 19:28:41 +01:00
currTime := UTCNow ( )
2016-10-12 08:46:51 +02: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 07:32:46 +01:00
return req , err
2016-10-12 08:46:51 +02:00
}
2017-03-28 02:02:04 +02: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 08:46:51 +02: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 19:28:41 +01:00
currTime := UTCNow ( )
2016-10-12 08:46:51 +02: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 07:32:46 +01:00
return req , err
2016-10-12 08:46:51 +02:00
}
2016-11-11 06:57:15 +01: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" )
}
2019-10-23 07:59:13 +02:00
region := globalServerRegion
2017-03-18 19:28:41 +01:00
date := UTCNow ( )
2017-02-06 22:09:09 +01:00
scope := getScope ( date , region )
credential := fmt . Sprintf ( "%s/%s" , accessKeyID , scope )
2016-11-11 06:57:15 +01: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-06 02:00:24 +02: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 06:57:15 +01:00
queryStr := strings . Replace ( query . Encode ( ) , "+" , "%20" , - 1 )
2017-04-06 02:00:24 +02:00
canonicalRequest := getCanonicalRequest ( extractedSignedHeaders , unsignedPayload , queryStr , req . URL . Path , req . Method )
2017-02-06 22:09:09 +01:00
stringToSign := getStringToSign ( canonicalRequest , date , scope )
2019-02-28 02:46:55 +01:00
signingKey := getSigningKey ( secretAccessKey , date , region , serviceS3 )
2016-11-11 06:57:15 +01:00
signature := getSignature ( signingKey , stringToSign )
req . URL . RawQuery = query . Encode ( )
// Add signature header to RawQuery.
2017-03-26 20:56:17 +02:00
req . URL . RawQuery += "&X-Amz-Signature=" + url . QueryEscape ( signature )
2016-11-11 06:57:15 +01:00
// Construct the final presigned URL.
return nil
}
2016-09-30 23:32:13 +02: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 20:00:07 +02: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 23:32:13 +02:00
return nil
}
// Sign given request using Signature V2.
func signRequestV2 ( req * http . Request , accessKey , secretKey string ) error {
2019-02-13 13:59:36 +01:00
s3signer . SignV2 ( * req , accessKey , secretKey , false )
2016-09-30 23:32:13 +02:00
return nil
}
2016-07-10 20:10:59 +02:00
// Sign given request using Signature V4.
2016-09-30 23:32:13 +02:00
func signRequestV4 ( req * http . Request , accessKey , secretKey string ) error {
2016-07-10 20:10:59 +02:00
// Get hashed payload.
hashedPayload := req . Header . Get ( "x-amz-content-sha256" )
if hashedPayload == "" {
2016-11-16 03:14:23 +01:00
return fmt . Errorf ( "Invalid hashed payload" )
2016-06-21 21:10:18 +02:00
}
2017-03-18 19:28:41 +01:00
currTime := UTCNow ( )
2016-06-21 21:10:18 +02:00
2016-07-10 20:10:59 +02:00
// Set x-amz-date.
req . Header . Set ( "x-amz-date" , currTime . Format ( iso8601Format ) )
2016-06-21 21:10:18 +02:00
2016-07-10 20:10:59 +02: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 21:10:18 +02:00
}
}
2016-07-10 20:10:59 +02:00
// Get header keys.
headers := [ ] string { "host" }
for k := range headerMap {
headers = append ( headers , k )
2016-06-21 21:10:18 +02:00
}
sort . Strings ( headers )
2019-10-23 07:59:13 +02:00
region := globalServerRegion
2016-09-15 08:53:42 +02:00
2016-07-10 20:10:59 +02:00
// Get canonical headers.
var buf bytes . Buffer
2016-06-21 21:10:18 +02:00
for _ , k := range headers {
2016-07-10 20:10:59 +02:00
buf . WriteString ( k )
buf . WriteByte ( ':' )
2016-06-21 21:10:18 +02:00
switch {
case k == "host" :
2016-07-10 20:10:59 +02:00
buf . WriteString ( req . URL . Host )
2016-06-21 21:10:18 +02:00
fallthrough
default :
2016-07-10 20:10:59 +02:00
for idx , v := range headerMap [ k ] {
2016-06-21 21:10:18 +02:00
if idx > 0 {
2016-07-10 20:10:59 +02:00
buf . WriteByte ( ',' )
2016-06-21 21:10:18 +02:00
}
2016-07-10 20:10:59 +02:00
buf . WriteString ( v )
2016-06-21 21:10:18 +02:00
}
2016-07-10 20:10:59 +02:00
buf . WriteByte ( '\n' )
2016-06-21 21:10:18 +02:00
}
}
2016-07-10 20:10:59 +02:00
canonicalHeaders := buf . String ( )
2016-06-21 21:10:18 +02:00
2016-07-10 20:10:59 +02:00
// Get signed headers.
2016-06-21 21:10:18 +02:00
signedHeaders := strings . Join ( headers , ";" )
2016-07-10 20:10:59 +02:00
// Get canonical query string.
2016-06-21 21:10:18 +02:00
req . URL . RawQuery = strings . Replace ( req . URL . Query ( ) . Encode ( ) , "+" , "%20" , - 1 )
2016-07-10 20:10:59 +02:00
// Get canonical URI.
2018-06-05 19:48:51 +02:00
canonicalURI := s3utils . EncodePath ( req . URL . Path )
2016-07-10 20:10:59 +02:00
// Get canonical request.
2016-06-21 21:10:18 +02:00
// canonicalRequest =
// <HTTPMethod>\n
// <CanonicalURI>\n
// <CanonicalQueryString>\n
// <CanonicalHeaders>\n
// <SignedHeaders>\n
// <HashedPayload>
//
canonicalRequest := strings . Join ( [ ] string {
req . Method ,
2016-07-10 20:10:59 +02:00
canonicalURI ,
2016-06-21 21:10:18 +02:00
req . URL . RawQuery ,
2016-07-10 20:10:59 +02:00
canonicalHeaders ,
2016-06-21 21:10:18 +02:00
signedHeaders ,
hashedPayload ,
} , "\n" )
2016-07-10 20:10:59 +02:00
// Get scope.
2016-06-21 21:10:18 +02:00
scope := strings . Join ( [ ] string {
2016-07-10 20:10:59 +02:00
currTime . Format ( yyyymmdd ) ,
2016-09-15 08:53:42 +02:00
region ,
2019-02-28 02:46:55 +01:00
string ( serviceS3 ) ,
2016-06-21 21:10:18 +02:00
"aws4_request" ,
2019-08-06 21:08:58 +02:00
} , SlashSeparator )
2016-06-21 21:10:18 +02:00
2016-07-10 20:10:59 +02:00
stringToSign := "AWS4-HMAC-SHA256" + "\n" + currTime . Format ( iso8601Format ) + "\n"
2016-06-21 21:10:18 +02:00
stringToSign = stringToSign + scope + "\n"
2016-11-21 22:51:05 +01:00
stringToSign = stringToSign + getSHA256Hash ( [ ] byte ( canonicalRequest ) )
2016-06-21 21:10:18 +02:00
2016-07-10 20:10:59 +02:00
date := sumHMAC ( [ ] byte ( "AWS4" + secretKey ) , [ ] byte ( currTime . Format ( yyyymmdd ) ) )
2016-09-15 08:53:42 +02:00
regionHMAC := sumHMAC ( date , [ ] byte ( region ) )
2019-02-28 02:46:55 +01:00
service := sumHMAC ( regionHMAC , [ ] byte ( serviceS3 ) )
2016-06-21 21:10:18 +02:00
signingKey := sumHMAC ( service , [ ] byte ( "aws4_request" ) )
signature := hex . EncodeToString ( sumHMAC ( signingKey , [ ] byte ( stringToSign ) ) )
// final Authorization header
parts := [ ] string {
2019-08-06 21:08:58 +02:00
"AWS4-HMAC-SHA256" + " Credential=" + accessKey + SlashSeparator + scope ,
2016-06-21 21:10:18 +02:00
"SignedHeaders=" + signedHeaders ,
"Signature=" + signature ,
}
auth := strings . Join ( parts , ", " )
req . Header . Set ( "Authorization" , auth )
2016-07-10 20:10:59 +02:00
return nil
}
2016-12-27 17:28:10 +01:00
// getCredentialString generate a credential string.
func getCredentialString ( accessKeyID , location string , t time . Time ) string {
2019-08-06 21:08:58 +02:00
return accessKeyID + SlashSeparator + getScope ( t , location )
2016-09-14 04:00:01 +02:00
}
2018-04-15 15:56:04 +02: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 20:10:59 +02:00
// Returns new HTTP request object.
func newTestRequest ( method , urlStr string , contentLength int64 , body io . ReadSeeker ) ( * http . Request , error ) {
if method == "" {
method = "POST"
}
// Save for subsequent use
var hashedPayload string
2017-08-06 20:27:33 +02:00
var md5Base64 string
2016-07-10 20:10:59 +02:00
switch {
case body == nil :
2016-11-21 22:51:05 +01:00
hashedPayload = getSHA256Hash ( [ ] byte { } )
2016-07-10 20:10:59 +02:00
default :
2016-09-30 23:32:13 +02:00
payloadBytes , err := ioutil . ReadAll ( body )
if err != nil {
return nil , err
2016-07-10 20:10:59 +02:00
}
2016-11-21 22:51:05 +01:00
hashedPayload = getSHA256Hash ( payloadBytes )
2017-08-06 20:27:33 +02:00
md5Base64 = getMD5HashBase64 ( payloadBytes )
2016-07-10 20:10:59 +02:00
}
// Seek back to beginning.
if body != nil {
body . Seek ( 0 , 0 )
} else {
2017-08-06 20:27:33 +02:00
body = bytes . NewReader ( [ ] byte ( "" ) )
2016-07-10 20:10:59 +02:00
}
2017-08-06 20:27:33 +02: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 20:10:59 +02:00
return req , nil
}
2016-10-25 22:34:14 +02:00
// Various signature types we are supporting, currently
// two main signature types.
type signerType int
2016-09-30 23:32:13 +02:00
2016-10-25 22:34:14 +02:00
const (
signerV2 signerType = iota
signerV4
)
2016-09-30 23:32:13 +02:00
2016-10-25 22:34:14 +02:00
func newTestSignedRequest ( method , urlStr string , contentLength int64 , body io . ReadSeeker , accessKey , secretKey string , signer signerType ) ( * http . Request , error ) {
if signer == signerV2 {
2018-09-21 04:22:09 +02:00
return newTestSignedRequestV2 ( method , urlStr , contentLength , body , accessKey , secretKey , nil )
2016-09-30 23:32:13 +02:00
}
2018-09-21 04:22:09 +02:00
return newTestSignedRequestV4 ( method , urlStr , contentLength , body , accessKey , secretKey , nil )
2016-09-30 23:32:13 +02:00
}
2017-04-10 18:58:08 +02: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 23:32:13 +02:00
// Returns new HTTP request object signed with signature v2.
2018-09-21 04:22:09 +02:00
func newTestSignedRequestV2 ( method , urlStr string , contentLength int64 , body io . ReadSeeker , accessKey , secretKey string , headers map [ string ] string ) ( * http . Request , error ) {
2016-09-30 23:32:13 +02: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-21 04:22:09 +02:00
for k , v := range headers {
req . Header . Add ( k , v )
}
2016-09-30 23:32:13 +02:00
err = signRequestV2 ( req , accessKey , secretKey )
if err != nil {
return nil , err
}
return req , nil
}
2016-07-10 20:10:59 +02:00
// Returns new HTTP request object signed with signature v4.
2018-09-21 04:22:09 +02:00
func newTestSignedRequestV4 ( method , urlStr string , contentLength int64 , body io . ReadSeeker , accessKey , secretKey string , headers map [ string ] string ) ( * http . Request , error ) {
2016-07-10 20:10:59 +02:00
req , err := newTestRequest ( method , urlStr , contentLength , body )
if err != nil {
return nil , err
}
2016-09-09 19:18:38 +02:00
// Anonymous request return quickly.
if accessKey == "" || secretKey == "" {
return req , nil
}
2018-09-21 04:22:09 +02:00
for k , v := range headers {
req . Header . Add ( k , v )
}
2016-09-30 23:32:13 +02:00
err = signRequestV4 ( req , accessKey , secretKey )
2016-07-10 20:10:59 +02:00
if err != nil {
return nil , err
}
2016-06-21 21:10:18 +02:00
return req , nil
}
2016-08-16 01:13:03 +02:00
// Return new WebRPC request object.
func newWebRPCRequest ( methodRPC , authorization string , body io . ReadSeeker ) ( * http . Request , error ) {
req , err := http . NewRequest ( "POST" , "/minio/webrpc" , nil )
if err != nil {
return nil , err
}
2019-08-13 02:05:30 +02:00
req . Header . Set ( "User-Agent" , "Mozilla" )
2016-08-16 01:13:03 +02:00
req . Header . Set ( "Content-Type" , "application/json" )
if authorization != "" {
req . Header . Set ( "Authorization" , "Bearer " + authorization )
}
// Seek back to beginning.
if body != nil {
body . Seek ( 0 , 0 )
// Add body
req . Body = ioutil . NopCloser ( body )
} else {
// 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 ( "" ) ) )
}
return req , nil
}
// Marshal request and return a new HTTP request object to call the webrpc
func newTestWebRPCRequest ( rpcMethod string , authorization string , data interface { } ) ( * http . Request , error ) {
type genericJSON struct {
JSONRPC string ` json:"jsonrpc" `
ID string ` json:"id" `
Method string ` json:"method" `
Params interface { } ` json:"params" `
}
encapsulatedData := genericJSON { JSONRPC : "2.0" , ID : "1" , Method : rpcMethod , Params : data }
jsonData , err := json . Marshal ( encapsulatedData )
2016-09-02 08:10:50 +02:00
if err != nil {
return nil , err
}
2016-08-16 01:13:03 +02:00
req , err := newWebRPCRequest ( rpcMethod , authorization , bytes . NewReader ( jsonData ) )
if err != nil {
return nil , err
}
return req , nil
}
type ErrWebRPC struct {
Code int ` json:"code" `
Message string ` json:"message" `
Data interface { } ` json:"data" `
}
// Unmarshal response and return the webrpc response
func getTestWebRPCResponse ( resp * httptest . ResponseRecorder , data interface { } ) error {
type rpcReply struct {
ID string ` json:"id" `
JSONRPC string ` json:"jsonrpc" `
Result interface { } ` json:"result" `
Error * ErrWebRPC ` json:"error" `
}
reply := & rpcReply { Result : & data }
err := json . NewDecoder ( resp . Body ) . Decode ( reply )
if err != nil {
return err
}
// For the moment, web handlers errors code are not meaningful
// Return only the error message
if reply . Error != nil {
return errors . New ( reply . Error . Message )
}
return nil
}
2017-03-18 19:28:41 +01:00
var src = rand . NewSource ( UTCNow ( ) . UnixNano ( ) )
2016-06-26 04:07:44 +02:00
// Function to generate random string for bucket/object names.
func randString ( n int ) string {
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 23:28:06 +02:00
// generate random object name.
func getRandomObjectName ( ) string {
return randString ( 16 )
}
2016-06-26 04:07:44 +02:00
// generate random bucket name.
func getRandomBucketName ( ) string {
return randString ( 60 )
}
2016-07-09 03:26:04 +02:00
// NewEOFWriter returns a Writer that writes to w,
// but returns EOF error after writing n bytes.
func NewEOFWriter ( w io . Writer , n int64 ) io . Writer {
return & EOFWriter { w , n }
}
type EOFWriter struct {
w io . Writer
n int64
}
2016-07-10 02:11:08 +02:00
// io.Writer implementation designed to error out with io.EOF after reading `n` bytes.
2016-07-09 03:26:04 +02:00
func ( t * EOFWriter ) Write ( p [ ] byte ) ( n int , err error ) {
if t . n <= 0 {
return - 1 , io . EOF
}
// real write
n = len ( p )
if int64 ( n ) > t . n {
n = int ( t . n )
}
n , err = t . w . Write ( p [ 0 : n ] )
t . n -= int64 ( n )
if err == nil {
n = len ( p )
}
return
}
2016-06-26 04:07:44 +02:00
// construct URL for http requests for bucket operations.
func makeTestTargetURL ( endPoint , bucketName , objectName string , queryValues url . Values ) string {
2019-08-06 21:08:58 +02:00
urlStr := endPoint + SlashSeparator
2016-06-26 04:07:44 +02:00
if bucketName != "" {
2019-08-06 21:08:58 +02:00
urlStr = urlStr + bucketName + SlashSeparator
2016-06-26 04:07:44 +02:00
}
if objectName != "" {
2018-06-05 19:48:51 +02:00
urlStr = urlStr + s3utils . EncodePath ( objectName )
2016-06-26 04:07:44 +02:00
}
if len ( queryValues ) > 0 {
2017-03-26 20:56:17 +02:00
urlStr = urlStr + "?" + queryValues . Encode ( )
2016-06-26 04:07:44 +02: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 17:23:26 +02: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 18:38:34 +01: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-26 04:07:44 +02: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 10:59:08 +02: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-09-22 02:41:34 +02:00
2016-09-02 10:59:08 +02:00
}
2016-08-17 04:24:23 +02:00
// return URL for HEAD on the object.
2016-06-26 04:07:44 +02:00
func getHeadObjectURL ( endPoint , bucketName , objectName string ) string {
return makeTestTargetURL ( endPoint , bucketName , objectName , url . Values { } )
}
2016-08-17 04:24:23 +02: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 07:01:58 +02: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-26 04:07:44 +02: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 07:35:30 +02:00
queryValue := url . Values { }
queryValue . Set ( "policy" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
2016-06-26 04:07:44 +02: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 19:18:38 +02:00
}
2016-06-26 04:07:44 +02:00
2016-11-16 18:46:09 +01: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 19:18:38 +02: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-26 04:07:44 +02:00
}
2016-07-17 21:32:05 +02:00
// return URL for listing objects in the bucket with V1 legacy API.
2019-02-24 07:14:24 +01:00
func getListObjectsV1URL ( endPoint , bucketName , prefix , maxKeys , encodingType string ) string {
2016-06-28 08:54:56 +02:00
queryValue := url . Values { }
if maxKeys != "" {
queryValue . Set ( "max-keys" , maxKeys )
}
2019-02-24 07:14:24 +01:00
if encodingType != "" {
queryValue . Set ( "encoding-type" , encodingType )
}
return makeTestTargetURL ( endPoint , bucketName , prefix , queryValue )
2016-06-28 08:54:56 +02:00
}
2016-07-17 21:32:05 +02:00
// return URL for listing objects in the bucket with V2 API.
2019-02-24 07:14:24 +01:00
func getListObjectsV2URL ( endPoint , bucketName , prefix , maxKeys , fetchOwner , encodingType string ) string {
2016-07-17 21:32:05 +02:00
queryValue := url . Values { }
queryValue . Set ( "list-type" , "2" ) // Enables list objects V2 URL.
if maxKeys != "" {
queryValue . Set ( "max-keys" , maxKeys )
}
2016-09-10 19:44:38 +02:00
if fetchOwner != "" {
queryValue . Set ( "fetch-owner" , fetchOwner )
}
2019-02-24 07:14:24 +01:00
if encodingType != "" {
queryValue . Set ( "encoding-type" , encodingType )
}
return makeTestTargetURL ( endPoint , bucketName , prefix , queryValue )
2016-07-17 21:32:05 +02:00
}
2016-06-28 08:54:56 +02: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-14 04:00:01 +02:00
// return URL for a listing pending multipart uploads.
2016-06-28 08:54:56 +02:00
func getListMultipartURL ( endPoint , bucketName string ) string {
queryValue := url . Values { }
queryValue . Set ( "uploads" , "" )
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
2016-09-14 04:00:01 +02: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 17:54:57 +02:00
func getListMultipartURLWithParams ( endPoint , bucketName , objectName , uploadID , maxParts , partNumberMarker , encoding string ) string {
2016-06-28 08:54:56 +02:00
queryValues := url . Values { }
queryValues . Set ( "uploadId" , uploadID )
queryValues . Set ( "max-parts" , maxParts )
2016-10-03 17:54:57 +02:00
if partNumberMarker != "" {
queryValues . Set ( "part-number-marker" , partNumberMarker )
}
2016-06-28 08:54:56 +02: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 10:08:03 +02:00
// return URL for listen bucket notification.
2016-10-12 20:02:15 +02:00
func getListenBucketNotificationURL ( endPoint , bucketName string , prefixes , suffixes , events [ ] string ) string {
2016-09-28 10:08:03 +02:00
queryValue := url . Values { }
2016-10-12 20:02:15 +02:00
queryValue [ "prefix" ] = prefixes
queryValue [ "suffix" ] = suffixes
2016-09-28 10:08:03 +02:00
queryValue [ "events" ] = events
return makeTestTargetURL ( endPoint , bucketName , "" , queryValue )
}
2016-06-21 21:10:18 +02:00
// returns temp root directory. `
func getTestRoot ( ) ( string , error ) {
2016-12-16 07:25:05 +01:00
return ioutil . TempDir ( globalTestTmpDir , "api-" )
2016-06-21 21:10:18 +02:00
}
2016-08-31 04:22:27 +02:00
// getRandomDisks - Creates a slice of N random disks, each of the form - minio-XXX
func getRandomDisks ( N int ) ( [ ] string , error ) {
2016-06-08 03:15:04 +02:00
var erasureDisks [ ] string
2016-08-31 04:22:27 +02:00
for i := 0 ; i < N ; i ++ {
2016-12-16 07:25:05 +01:00
path , err := ioutil . TempDir ( globalTestTmpDir , "minio-" )
2016-05-06 20:57:04 +02:00
if err != nil {
2016-08-31 04:22:27 +02:00
// Remove directories created so far.
removeRoots ( erasureDisks )
return nil , err
2016-05-06 20:57:04 +02:00
}
2016-06-08 03:15:04 +02:00
erasureDisks = append ( erasureDisks , path )
2016-05-06 20:57:04 +02:00
}
2016-08-31 04:22:27 +02:00
return erasureDisks , nil
}
2016-05-06 20:57:04 +02:00
2018-02-16 02:45:57 +01:00
// Initialize object layer with the supplied disks, objectLayer is nil upon any error.
2019-11-20 02:42:27 +01:00
func newTestObjectLayer ( endpointZones EndpointZones ) ( newObject ObjectLayer , err error ) {
2018-02-16 02:45:57 +01:00
// For FS only, directly use the disk.
2019-11-20 02:42:27 +01:00
if endpointZones . Nodes ( ) == 1 {
2018-02-16 02:45:57 +01:00
// Initialize new FS object layer.
2019-11-20 02:42:27 +01:00
return NewFSObjectLayer ( endpointZones [ 0 ] . Endpoints [ 0 ] . Path )
2018-02-16 02:45:57 +01:00
}
2019-11-21 13:24:51 +01:00
z , err := newXLZones ( endpointZones )
2019-11-20 02:42:27 +01:00
if err != nil {
return nil , err
2019-11-13 21:17:45 +01:00
}
2018-02-16 02:45:57 +01:00
2018-10-09 23:00:01 +02:00
globalConfigSys = NewConfigSys ( )
globalIAMSys = NewIAMSys ( )
2019-11-21 13:24:51 +01:00
globalIAMSys . Init ( z )
2016-10-05 21:48:07 +02:00
2018-04-25 00:53:30 +02:00
globalPolicySys = NewPolicySys ( )
2019-11-21 13:24:51 +01:00
globalPolicySys . Init ( nil , z )
2019-11-09 18:27:23 +01:00
2019-11-20 02:42:27 +01:00
globalNotificationSys = NewNotificationSys ( endpointZones )
2019-11-21 13:24:51 +01:00
globalNotificationSys . Init ( nil , z )
2018-04-25 00:53:30 +02:00
2019-11-21 13:24:51 +01:00
return z , nil
2018-02-16 02:45:57 +01:00
}
// initObjectLayer - Instantiates object layer and returns it.
2019-11-20 02:42:27 +01:00
func initObjectLayer ( endpointZones EndpointZones ) ( ObjectLayer , [ ] StorageAPI , error ) {
objLayer , err := newTestObjectLayer ( endpointZones )
2016-10-05 21:48:07 +02:00
if err != nil {
return nil , nil , err
2016-06-08 03:15:04 +02:00
}
2016-10-05 21:48:07 +02:00
2018-02-16 02:45:57 +01:00
var formattedDisks [ ] StorageAPI
// Should use the object layer tests for validating cache.
2019-11-20 02:42:27 +01:00
if z , ok := objLayer . ( * xlZones ) ; ok {
formattedDisks = z . zones [ 0 ] . GetDisks ( 0 ) ( )
2018-02-16 02:45:57 +01:00
}
2016-10-05 21:48:07 +02:00
// Success.
2016-11-24 00:48:10 +01:00
return objLayer , formattedDisks , nil
2016-06-08 03:15:04 +02:00
}
// removeRoots - Cleans up initialized directories during tests.
func removeRoots ( roots [ ] string ) {
for _ , root := range roots {
2017-08-13 04:25:43 +02:00
os . RemoveAll ( root )
2016-05-06 20:57:04 +02:00
}
2016-06-08 03:15:04 +02:00
}
2016-05-06 20:57:04 +02:00
2016-06-29 07:32:00 +02:00
//removeDiskN - removes N disks from supplied disk slice.
func removeDiskN ( disks [ ] string , n int ) {
if n > len ( disks ) {
n = len ( disks )
}
for _ , disk := range disks [ : n ] {
2017-08-13 04:25:43 +02:00
os . RemoveAll ( disk )
2016-06-08 03:15:04 +02:00
}
}
2016-09-10 23:47:27 +02: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.
2017-01-24 02:01:44 +01:00
func initAPIHandlerTest ( obj ObjectLayer , endpoints [ ] string ) ( string , http . Handler , error ) {
2016-09-10 23:47:27 +02:00
// get random bucket name.
2017-01-24 02:01:44 +01:00
bucketName := getRandomBucketName ( )
2016-09-10 23:47:27 +02:00
// Create bucket.
2018-03-15 21:27:16 +01:00
err := obj . MakeBucketWithLocation ( context . Background ( ) , bucketName , "" )
2016-09-10 23:47:27 +02:00
if err != nil {
// failed to create newbucket, return err.
2016-10-21 10:25:17 +02:00
return "" , nil , err
2016-09-10 23:47:27 +02:00
}
2017-01-17 02:05:00 +01:00
// Register the API end points with XL object layer.
2016-09-10 23:47:27 +02:00
// Registering only the GetObject handler.
2017-01-24 02:01:44 +01:00
apiRouter := initTestAPIEndPoints ( obj , endpoints )
2019-02-13 13:59:36 +01:00
f := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2017-01-24 02:01:44 +01:00
r . RequestURI = r . URL . RequestURI ( )
apiRouter . ServeHTTP ( w , r )
2019-02-13 13:59:36 +01:00
} )
2017-01-24 02:01:44 +01:00
return bucketName , f , nil
2016-09-10 23:47:27 +02:00
}
2017-03-01 03:05:52 +01:00
// prepare test backend.
2018-02-16 02:45:57 +01:00
// create FS/XL/XLSet backend.
2017-03-01 03:05:52 +01:00
// return object layer, backend disks.
func prepareTestBackend ( instanceType string ) ( ObjectLayer , [ ] string , error ) {
switch instanceType {
2018-02-16 02:45:57 +01:00
// Total number of disks for XL sets backend is set to 32.
case XLSetsTestStr :
2019-05-22 22:36:16 +02:00
return prepareXLSets32 ( )
2017-03-01 03:05:52 +01:00
// Total number of disks for XL backend is set to 16.
case XLTestStr :
2017-12-28 18:32:48 +01:00
return prepareXL16 ( )
2017-03-01 03:05:52 +01: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-12 05:38:10 +02:00
// ExecObjectLayerAPIAnonTest - Helper function to validate object Layer API handler
// response for anonymous/unsigned and unknown signature type HTTP request.
2016-10-07 20:16:11 +02: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.
2016-10-12 05:38:10 +02:00
// STEP 2: Set the policy to allow the unsigned request, use the policyFunc to obtain the relevant statement and call
// the handler again to verify its success.
2018-02-10 00:19:30 +01:00
func ExecObjectLayerAPIAnonTest ( t * testing . T , obj ObjectLayer , testName , bucketName , objectName , instanceType string , apiRouter http . Handler ,
2018-04-25 00:53:30 +02:00
anonReq * http . Request , bucketPolicy * policy . Policy ) {
2016-10-12 05:38:10 +02:00
anonTestStr := "Anonymous HTTP request test"
unknownSignTestStr := "Unknown HTTP signature test"
2016-11-21 08:41:39 +01:00
// simple function which returns a message which gives the context of the test
2016-10-07 20:16:11 +02:00
// and then followed by the the actual error message.
2016-11-21 08:41:39 +01:00
failTestStr := func ( testType , failMsg string ) string {
2019-04-09 20:39:42 +02:00
return fmt . Sprintf ( "MinIO %s: %s fail for \"%s\": \n<Error> %s" , instanceType , testType , testName , failMsg )
2016-10-07 20:16:11 +02:00
}
2017-02-07 21:51:43 +01:00
2016-10-07 20:16:11 +02: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 08:41:39 +01:00
t . Fatal ( failTestStr ( anonTestStr , err . Error ( ) ) )
2016-10-07 20:16:11 +02:00
}
2016-10-09 18:21:37 +02:00
2016-10-07 20:16:11 +02: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 ) )
2016-10-12 05:38:10 +02:00
readerThree := ioutil . NopCloser ( bytes . NewBuffer ( buf ) )
2016-10-07 20:16:11 +02:00
anonReq . Body = readerOne
// call the HTTP handler.
apiRouter . ServeHTTP ( rec , anonReq )
// expected error response when the unsigned HTTP request is not permitted.
accesDeniedHTTPStatus := getAPIError ( ErrAccessDenied ) . HTTPStatusCode
if rec . Code != accesDeniedHTTPStatus {
2016-11-21 08:41:39 +01:00
t . Fatal ( failTestStr ( anonTestStr , fmt . Sprintf ( "Object API Nil Test expected to fail with %d, but failed with %d" , accesDeniedHTTPStatus , rec . Code ) ) )
2016-10-07 20:16:11 +02:00
}
2016-10-09 18:21:37 +02:00
// HEAD HTTTP request doesn't contain response body.
if anonReq . Method != "HEAD" {
// read the response body.
2018-04-25 00:53:30 +02:00
var actualContent [ ] byte
actualContent , err = ioutil . ReadAll ( rec . Body )
2016-10-09 18:21:37 +02:00
if err != nil {
2016-11-21 08:41:39 +01:00
t . Fatal ( failTestStr ( anonTestStr , fmt . Sprintf ( "Failed parsing response body: <ERROR> %v" , err ) ) )
2016-10-09 18:21:37 +02:00
}
2019-02-14 01:07:21 +01: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 18:21:37 +02:00
}
2016-10-07 20:16:11 +02:00
}
2018-04-25 00:53:30 +02:00
if err := obj . SetBucketPolicy ( context . Background ( ) , bucketName , bucketPolicy ) ; err != nil {
t . Fatalf ( "unexpected error. %v" , err )
2016-10-07 20:16:11 +02:00
}
2018-04-25 00:53:30 +02:00
globalPolicySys . Set ( bucketName , * bucketPolicy )
defer globalPolicySys . Remove ( bucketName )
2016-10-07 20:16:11 +02:00
// now call the handler again with the unsigned/anonymous request, it should be accepted.
rec = httptest . NewRecorder ( )
anonReq . Body = readerTwo
apiRouter . ServeHTTP ( rec , anonReq )
2016-10-08 08:28:50 +02:00
var expectedHTTPStatus int
// expectedHTTPStatus returns 204 (http.StatusNoContent) on success.
2016-11-09 01:25:00 +01:00
if testName == "TestAPIDeleteObjectHandler" || testName == "TestAPIAbortMultipartHandler" {
2016-10-08 08:28:50 +02:00
expectedHTTPStatus = http . StatusNoContent
2016-10-09 18:21:37 +02:00
} else if strings . Contains ( testName , "BucketPolicyHandler" ) || testName == "ListBucketsHandler" {
// BucketPolicyHandlers and `ListBucketsHandler` doesn't support anonymous request, policy changes should allow unsigned requests.
2016-10-08 10:04:26 +02:00
expectedHTTPStatus = http . StatusForbidden
2016-10-08 08:28:50 +02:00
} else {
// other API handlers return 200OK on success.
expectedHTTPStatus = http . StatusOK
}
2016-10-08 10:04:26 +02:00
2016-10-08 08:28:50 +02:00
// compare the HTTP response status code with the expected one.
if rec . Code != expectedHTTPStatus {
2016-11-21 08:41:39 +01:00
t . Fatal ( failTestStr ( anonTestStr , fmt . Sprintf ( "Expected the anonymous HTTP request to be served after the policy changes\n,Expected response HTTP status code to be %d, got %d" ,
expectedHTTPStatus , rec . Code ) ) )
2016-10-07 20:16:11 +02:00
}
2016-10-12 05:38:10 +02:00
// test for unknown auth case.
anonReq . Body = readerThree
// 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 =.
if anonReq . Method != "HEAD" {
// read the response body.
actualContent , err := ioutil . ReadAll ( rec . Body )
if err != nil {
2016-11-21 08:41:39 +01:00
t . Fatal ( failTestStr ( unknownSignTestStr , fmt . Sprintf ( "Failed parsing response body: <ERROR> %v" , err ) ) )
2016-10-12 05:38:10 +02:00
}
2019-02-14 01:07:21 +01:00
actualError := & APIErrorResponse { }
if err = xml . Unmarshal ( actualContent , actualError ) ; err != nil {
t . Fatal ( failTestStr ( unknownSignTestStr , "error response failed to parse error XML" ) )
}
if actualError . BucketName != bucketName {
t . Fatal ( failTestStr ( unknownSignTestStr , "error response bucket name differs from expected value" ) )
}
if actualError . Key != objectName {
t . Fatal ( failTestStr ( unknownSignTestStr , "error response object name differs from expected value" ) )
2016-10-12 05:38:10 +02:00
}
}
if rec . Code != accesDeniedHTTPStatus {
2016-11-21 08:41:39 +01:00
t . Fatal ( failTestStr ( unknownSignTestStr , fmt . Sprintf ( "Object API Unknow auth test for \"%s\", expected to fail with %d, but failed with %d" , testName , accesDeniedHTTPStatus , rec . Code ) ) )
2016-10-12 05:38:10 +02:00
}
2018-02-10 00:19:30 +01:00
2016-10-07 20:16:11 +02:00
}
2017-01-31 18:38:34 +01: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 22:34:33 +02: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 08:03:10 +02:00
globalObjLayerMutex . Lock ( )
2016-10-06 22:34:33 +02:00
globalObjectAPI = nil
2016-10-10 08:03:10 +02:00
globalObjLayerMutex . Unlock ( )
2017-01-31 18:38:34 +01:00
2016-10-06 22:34:33 +02: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-16 03:14:23 +01:00
t . Errorf ( "Object API Nil Test expected to fail with %d, but failed with %d" , serverNotInitializedErr , rec . Code )
2016-10-06 22:34:33 +02:00
}
2016-10-11 09:00:02 +02: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.
if req . Method != "HEAD" {
// read the response body.
actualContent , err := ioutil . ReadAll ( rec . Body )
if err != nil {
2019-04-09 20:39:42 +02:00
t . Fatalf ( "MinIO %s: Failed parsing response body: <ERROR> %v" , instanceType , err )
2016-10-11 09:00:02 +02:00
}
2019-02-14 01:07:21 +01:00
actualError := & APIErrorResponse { }
if err = xml . Unmarshal ( actualContent , actualError ) ; err != nil {
2019-04-09 20:39:42 +02:00
t . Errorf ( "MinIO %s: error response failed to parse error XML" , instanceType )
2019-02-14 01:07:21 +01:00
}
if actualError . BucketName != bucketName {
2019-04-09 20:39:42 +02:00
t . Errorf ( "MinIO %s: error response bucket name differs from expected value" , instanceType )
2019-02-14 01:07:21 +01:00
}
if actualError . Key != objectName {
2019-04-09 20:39:42 +02:00
t . Errorf ( "MinIO %s: error response object name differs from expected value" , instanceType )
2016-10-11 09:00:02 +02:00
}
2016-10-06 22:34:33 +02:00
}
}
2016-09-10 23:47:27 +02:00
// ExecObjectLayerAPITest - executes object layer API tests.
// Creates single node and XL ObjectLayer instance, registers the specified API end points and runs test for both the layers.
2016-10-27 12:30:52 +02:00
func ExecObjectLayerAPITest ( t * testing . T , objAPITest objAPITestType , endpoints [ ] string ) {
2017-01-07 20:27:01 +01:00
// reset globals.
// this is to make sure that the tests are not affected by modified value.
resetTestGlobals ( )
2017-02-07 21:51:43 +01:00
2016-09-10 23:47:27 +02:00
objLayer , fsDir , err := prepareFS ( )
if err != nil {
t . Fatalf ( "Initialization of object layer failed for single node setup: %s" , err )
}
2016-10-27 12:30:52 +02:00
bucketFS , fsAPIRouter , err := initAPIHandlerTest ( objLayer , endpoints )
2016-09-10 23:47:27 +02:00
if err != nil {
2018-10-18 02:25:50 +02:00
t . Fatalf ( "Initialization of API handler tests failed: <ERROR> %s" , err )
2016-09-10 23:47:27 +02:00
}
2018-08-15 06:41:47 +02: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 )
}
2020-01-07 18:13:44 +01:00
newAllSubsystems ( )
2019-09-10 01:12:29 +02:00
globalIAMSys . Init ( objLayer )
2019-10-02 02:05:02 +02:00
buckets , err := objLayer . ListBuckets ( context . Background ( ) )
if err != nil {
t . Fatalf ( "Unable to list buckets on backend %s" , err )
}
globalPolicySys . Init ( buckets , objLayer )
2019-09-10 01:12:29 +02:00
2019-10-23 07:59:13 +02:00
credentials := globalActiveCred
2018-08-15 06:41:47 +02:00
2016-09-10 23:47:27 +02:00
// Executing the object layer tests for single node setup.
2016-11-01 18:21:16 +01:00
objAPITest ( objLayer , FSTestStr , bucketFS , fsAPIRouter , credentials , t )
2016-09-10 23:47:27 +02:00
2017-12-28 18:32:48 +01:00
objLayer , xlDisks , err := prepareXL16 ( )
2016-09-10 23:47:27 +02:00
if err != nil {
t . Fatalf ( "Initialization of object layer failed for XL setup: %s" , err )
}
2016-10-27 12:30:52 +02:00
bucketXL , xlAPIRouter , err := initAPIHandlerTest ( objLayer , endpoints )
2016-09-10 23:47:27 +02:00
if err != nil {
t . Fatalf ( "Initialzation of API handler tests failed: <ERROR> %s" , err )
}
// Executing the object layer tests for XL.
2016-11-01 18:21:16 +01:00
objAPITest ( objLayer , XLTestStr , bucketXL , xlAPIRouter , credentials , t )
2016-10-06 22:34:33 +02:00
// clean up the temporary test backend.
2018-08-15 06:41:47 +02:00
removeRoots ( append ( xlDisks , fsDir ) )
2016-09-10 23:47:27 +02: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 19:54:32 +01:00
apiRouter http . Handler , credentials auth . Credentials , t * testing . T )
2016-09-10 23:47:27 +02:00
2016-06-08 03:15:04 +02:00
// Regular object test type.
2016-07-08 00:05:51 +02:00
type objTestType func ( obj ObjectLayer , instanceType string , t TestErrHandler )
2016-06-08 03:15:04 +02:00
2017-12-22 12:28:13 +01:00
// Special test type for test with directories
type objTestTypeWithDirs func ( obj ObjectLayer , instanceType string , dirs [ ] string , t TestErrHandler )
2016-06-08 03:15:04 +02: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.
// Creates single node and XL ObjectLayer instance and runs test for both the layers.
2016-07-08 00:05:51 +02:00
func ExecObjectLayerTest ( t TestErrHandler , objTest objTestType ) {
2016-08-31 04:22:27 +02:00
objLayer , fsDir , err := prepareFS ( )
2016-05-06 20:57:04 +02:00
if err != nil {
2016-07-03 04:05:16 +02:00
t . Fatalf ( "Initialization of object layer failed for single node setup: %s" , err )
2016-05-06 20:57:04 +02:00
}
2017-10-22 07:30:34 +02:00
2018-08-15 06:41:47 +02: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 )
}
2019-10-23 07:59:13 +02:00
globalIAMSys = NewIAMSys ( )
globalIAMSys . Init ( objLayer )
buckets , err := objLayer . ListBuckets ( context . Background ( ) )
if err != nil {
t . Fatalf ( "Unable to list buckets on backend %s" , err )
}
globalPolicySys = NewPolicySys ( )
globalPolicySys . Init ( buckets , objLayer )
2016-05-26 23:43:17 +02:00
// Executing the object layer tests for single node setup.
2016-11-01 18:21:16 +01:00
objTest ( objLayer , FSTestStr , t )
2016-05-21 05:48:47 +02:00
2019-05-22 22:36:16 +02:00
objLayer , fsDirs , err := prepareXLSets32 ( )
2016-05-06 20:57:04 +02:00
if err != nil {
2016-07-03 04:05:16 +02:00
t . Fatalf ( "Initialization of object layer failed for XL setup: %s" , err )
2016-05-06 20:57:04 +02:00
}
// Executing the object layer tests for XL.
2016-11-01 18:21:16 +01:00
objTest ( objLayer , XLTestStr , t )
2016-05-06 20:57:04 +02:00
defer removeRoots ( append ( fsDirs , fsDir ) )
}
2016-06-08 03:15:04 +02:00
2017-12-22 12:28:13 +01:00
// ExecObjectLayerTestWithDirs - executes object layer tests.
// Creates single node and XL ObjectLayer instance and runs test for both the layers.
func ExecObjectLayerTestWithDirs ( t TestErrHandler , objTest objTestTypeWithDirs ) {
2018-08-15 06:41:47 +02:00
objLayer , fsDirs , err := prepareXL16 ( )
2017-12-22 12:28:13 +01:00
if err != nil {
2018-08-15 06:41:47 +02:00
t . Fatalf ( "Initialization of object layer failed for XL setup: %s" , err )
2017-12-22 12:28:13 +01:00
}
2018-08-15 06:41:47 +02: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 12:28:13 +01:00
}
// Executing the object layer tests for XL.
objTest ( objLayer , XLTestStr , fsDirs , t )
2018-08-15 06:41:47 +02:00
defer removeRoots ( fsDirs )
2017-12-22 12:28:13 +01:00
}
2016-10-21 01:09:55 +02:00
// ExecObjectLayerDiskAlteredTest - executes object layer tests while altering
2016-06-08 03:15:04 +02:00
// disks in between tests. Creates XL ObjectLayer instance and runs test for XL layer.
2016-10-21 01:09:55 +02:00
func ExecObjectLayerDiskAlteredTest ( t * testing . T , objTest objTestDiskNotFoundType ) {
2017-12-28 18:32:48 +01:00
objLayer , fsDirs , err := prepareXL16 ( )
2016-06-08 03:15:04 +02:00
if err != nil {
2016-07-03 04:05:16 +02:00
t . Fatalf ( "Initialization of object layer failed for XL setup: %s" , err )
2016-06-08 03:15:04 +02:00
}
2017-02-07 21:51:43 +01:00
2018-08-15 06:41:47 +02:00
if err = newTestConfig ( globalMinioDefaultRegion , objLayer ) ; err != nil {
t . Fatal ( "Failed to create config directory" , err )
}
2016-06-08 03:15:04 +02:00
// Executing the object layer tests for XL.
2016-11-01 18:21:16 +01:00
objTest ( objLayer , XLTestStr , fsDirs , t )
2016-06-08 03:15:04 +02:00
defer removeRoots ( fsDirs )
}
2016-06-29 11:28:46 +02: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
// files/directories under .minio/tmp. Creates XL ObjectLayer instance and runs test for XL layer.
func ExecObjectLayerStaleFilesTest ( t * testing . T , objTest objTestStaleFilesType ) {
2016-08-31 04:22:27 +02:00
nDisks := 16
erasureDisks , err := getRandomDisks ( nDisks )
if err != nil {
t . Fatalf ( "Initialization of disks for XL setup: %s" , err )
}
2019-11-20 02:42:27 +01:00
objLayer , _ , err := initObjectLayer ( mustGetZoneEndpoints ( erasureDisks ... ) )
2016-06-29 11:28:46 +02:00
if err != nil {
2016-07-03 04:05:16 +02:00
t . Fatalf ( "Initialization of object layer failed for XL setup: %s" , err )
2016-06-29 11:28:46 +02:00
}
2018-08-15 06:41:47 +02:00
if err = newTestConfig ( globalMinioDefaultRegion , objLayer ) ; err != nil {
t . Fatal ( "Failed to create config directory" , err )
}
2016-06-29 11:28:46 +02:00
// Executing the object layer tests for XL.
2016-11-01 18:21:16 +01:00
objTest ( objLayer , XLTestStr , erasureDisks , t )
2016-08-31 04:22:27 +02:00
defer removeRoots ( erasureDisks )
2016-06-29 11:28:46 +02:00
}
2016-07-03 04:05:16 +02:00
2018-04-22 04:23:54 +02:00
func registerBucketLevelFunc ( bucket * mux . Router , api objectAPIHandlers , apiFunctions ... string ) {
2016-10-05 21:48:07 +02:00
for _ , apiFunction := range apiFunctions {
switch apiFunction {
case "PostPolicy" :
// Register PostPolicy handler.
bucket . Methods ( "POST" ) . HeadersRegexp ( "Content-Type" , "multipart/form-data*" ) . HandlerFunc ( api . PostPolicyBucketHandler )
2016-11-08 01:02:27 +01:00
case "HeadObject" :
// Register HeadObject handler.
bucket . Methods ( "Head" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . HeadObjectHandler )
2016-10-05 21:48:07 +02:00
case "GetObject" :
2016-11-08 01:02:27 +01:00
// Register GetObject handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "GET" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . GetObjectHandler )
case "PutObject" :
2016-11-08 01:02:27 +01:00
// Register PutObject handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "PUT" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . PutObjectHandler )
case "DeleteObject" :
2016-11-08 01:02:27 +01:00
// Register Delete Object handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "DELETE" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . DeleteObjectHandler )
case "CopyObject" :
2016-11-08 01:02:27 +01:00
// Register Copy Object handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "PUT" ) . Path ( "/{object:.+}" ) . HeadersRegexp ( "X-Amz-Copy-Source" , ".*?(\\/|%2F).*?" ) . HandlerFunc ( api . CopyObjectHandler )
case "PutBucketPolicy" :
2016-11-08 01:02:27 +01:00
// Register PutBucket Policy handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "PUT" ) . HandlerFunc ( api . PutBucketPolicyHandler ) . Queries ( "policy" , "" )
case "DeleteBucketPolicy" :
2016-11-08 01:02:27 +01:00
// Register Delete bucket HTTP policy handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "DELETE" ) . HandlerFunc ( api . DeleteBucketPolicyHandler ) . Queries ( "policy" , "" )
case "GetBucketPolicy" :
2016-11-08 01:02:27 +01:00
// Register Get Bucket policy HTTP Handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "GET" ) . HandlerFunc ( api . GetBucketPolicyHandler ) . Queries ( "policy" , "" )
case "GetBucketLocation" :
2016-11-08 01:02:27 +01:00
// Register GetBucketLocation handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "GET" ) . HandlerFunc ( api . GetBucketLocationHandler ) . Queries ( "location" , "" )
case "HeadBucket" :
2016-11-08 01:02:27 +01:00
// Register HeadBucket handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "HEAD" ) . HandlerFunc ( api . HeadBucketHandler )
2016-11-16 18:46:09 +01:00
case "DeleteMultipleObjects" :
// Register DeleteMultipleObjects handler.
bucket . Methods ( "POST" ) . HandlerFunc ( api . DeleteMultipleObjectsHandler ) . Queries ( "delete" , "" )
2016-10-05 21:48:07 +02:00
case "NewMultipart" :
2016-11-08 01:02:27 +01:00
// Register New Multipart upload handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "POST" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . NewMultipartUploadHandler ) . Queries ( "uploads" , "" )
2017-01-31 18:38:34 +01:00
case "CopyObjectPart" :
// Register CopyObjectPart handler.
bucket . Methods ( "PUT" ) . Path ( "/{object:.+}" ) . HeadersRegexp ( "X-Amz-Copy-Source" , ".*?(\\/|%2F).*?" ) . HandlerFunc ( api . CopyObjectPartHandler ) . Queries ( "partNumber" , "{partNumber:[0-9]+}" , "uploadId" , "{uploadId:.*}" )
2016-10-05 21:48:07 +02:00
case "PutObjectPart" :
2016-11-08 01:02:27 +01:00
// Register PutObjectPart handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "PUT" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . PutObjectPartHandler ) . Queries ( "partNumber" , "{partNumber:[0-9]+}" , "uploadId" , "{uploadId:.*}" )
case "ListObjectParts" :
2016-11-08 01:02:27 +01:00
// Register ListObjectParts handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "GET" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . ListObjectPartsHandler ) . Queries ( "uploadId" , "{uploadId:.*}" )
case "ListMultipartUploads" :
2016-11-08 01:02:27 +01:00
// Register ListMultipartUploads handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "GET" ) . HandlerFunc ( api . ListMultipartUploadsHandler ) . Queries ( "uploads" , "" )
case "CompleteMultipart" :
2016-11-08 01:02:27 +01:00
// Register Complete Multipart Upload handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "POST" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . CompleteMultipartUploadHandler ) . Queries ( "uploadId" , "{uploadId:.*}" )
2016-11-09 01:25:00 +01:00
case "AbortMultipart" :
// Register AbortMultipart Handler.
bucket . Methods ( "DELETE" ) . Path ( "/{object:.+}" ) . HandlerFunc ( api . AbortMultipartUploadHandler ) . Queries ( "uploadId" , "{uploadId:.*}" )
2016-10-05 21:48:07 +02:00
case "GetBucketNotification" :
2016-11-08 01:02:27 +01:00
// Register GetBucketNotification Handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "GET" ) . HandlerFunc ( api . GetBucketNotificationHandler ) . Queries ( "notification" , "" )
case "PutBucketNotification" :
2016-11-08 01:02:27 +01:00
// Register PutBucketNotification Handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "PUT" ) . HandlerFunc ( api . PutBucketNotificationHandler ) . Queries ( "notification" , "" )
case "ListenBucketNotification" :
2016-11-08 01:02:27 +01:00
// Register ListenBucketNotification Handler.
2016-10-05 21:48:07 +02:00
bucket . Methods ( "GET" ) . HandlerFunc ( api . ListenBucketNotificationHandler ) . Queries ( "events" , "{events:.*}" )
}
2016-10-01 17:23:26 +02:00
}
}
2016-10-05 21:48:07 +02:00
// registerAPIFunctions helper function to add API functions identified by name to the routers.
2018-04-22 04:23:54 +02:00
func registerAPIFunctions ( muxRouter * mux . Router , objLayer ObjectLayer , apiFunctions ... string ) {
2016-10-05 21:48:07 +02:00
if len ( apiFunctions ) == 0 {
// Register all api endpoints by default.
2019-06-20 02:37:08 +02:00
registerAPIRouter ( muxRouter , true , false )
2016-10-05 21:48:07 +02:00
return
2016-10-01 17:23:26 +02:00
}
// API Router.
2019-08-06 21:08:58 +02:00
apiRouter := muxRouter . PathPrefix ( SlashSeparator ) . Subrouter ( )
2016-10-01 17:23:26 +02:00
// Bucket router.
2016-10-05 21:48:07 +02:00
bucketRouter := apiRouter . PathPrefix ( "/{bucket}" ) . Subrouter ( )
2016-10-01 17:23:26 +02:00
2016-07-03 04:05:16 +02:00
// All object storage operations are registered as HTTP handlers on `objectAPIHandlers`.
2018-03-28 23:14:06 +02:00
// When the handlers get a HTTP request they use the underlying ObjectLayer to perform operations.
2016-10-10 08:03:10 +02:00
globalObjLayerMutex . Lock ( )
2016-09-23 08:47:48 +02:00
globalObjectAPI = objLayer
2016-10-10 08:03:10 +02:00
globalObjLayerMutex . Unlock ( )
2016-09-23 08:47:48 +02:00
2018-03-28 23:14:06 +02: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-03 04:05:16 +02:00
api := objectAPIHandlers {
2019-10-31 07:39:09 +01:00
ObjectAPI : func ( ) ObjectLayer {
if ! globalSafeMode {
return globalObjectAPI
}
return nil
} ,
CacheAPI : func ( ) CacheObjectLayer {
if ! globalSafeMode {
return globalCacheObjectAPI
}
return nil
} ,
2019-01-05 23:16:43 +01:00
EncryptionEnabled : func ( ) bool { return true } ,
2016-07-03 04:05:16 +02:00
}
2016-09-23 08:47:48 +02:00
2016-10-05 21:48:07 +02:00
// Register ListBuckets handler.
apiRouter . Methods ( "GET" ) . HandlerFunc ( api . ListBucketsHandler )
// Register all bucket level handlers.
registerBucketLevelFunc ( bucketRouter , api , apiFunctions ... )
}
2017-01-17 02:05:00 +01:00
// Takes in XL 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 21:48:07 +02: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.
2018-04-22 04:23:54 +02:00
muxRouter := mux . NewRouter ( ) . SkipClean ( true )
2016-10-05 21:48:07 +02:00
if len ( apiFunctions ) > 0 {
// Iterate the list of API functions requested for and register them in mux HTTP handler.
registerAPIFunctions ( muxRouter , objLayer , apiFunctions ... )
return muxRouter
2016-07-03 04:05:16 +02:00
}
2019-06-20 02:37:08 +02:00
registerAPIRouter ( muxRouter , true , false )
2016-07-03 04:05:16 +02:00
return muxRouter
}
2016-08-16 01:13:03 +02:00
2016-08-29 04:31:59 +02:00
// Initialize Web RPC Handlers for testing
2016-08-16 01:13:03 +02:00
func initTestWebRPCEndPoint ( objLayer ObjectLayer ) http . Handler {
2016-10-10 08:03:10 +02:00
globalObjLayerMutex . Lock ( )
2016-10-05 21:48:07 +02:00
globalObjectAPI = objLayer
2016-10-10 08:03:10 +02:00
globalObjLayerMutex . Unlock ( )
2016-08-16 01:13:03 +02:00
// Initialize router.
2018-04-22 04:23:54 +02:00
muxRouter := mux . NewRouter ( ) . SkipClean ( true )
2016-10-05 21:48:07 +02:00
registerWebRouter ( muxRouter )
2016-08-16 01:13:03 +02:00
return muxRouter
}
2016-11-07 20:43:35 +01:00
2016-11-11 16:18:44 +01: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 18:28:01 +01:00
return nil , nil , fmt . Errorf ( "failed to generate private key: %w" , err )
2016-11-11 16:18:44 +01: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 18:28:01 +01:00
return nil , nil , fmt . Errorf ( "failed to generate serial number: %w" , err )
2016-11-11 16:18:44 +01: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 18:28:01 +01:00
return nil , nil , fmt . Errorf ( "Failed to create certificate: %w" , err )
2016-11-11 16:18:44 +01: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-12 00:44:27 +02:00
2019-11-20 02:42:27 +01:00
func mustGetZoneEndpoints ( args ... string ) EndpointZones {
endpoints := mustGetNewEndpoints ( args ... )
return [ ] ZoneEndpoints { {
SetCount : 1 ,
DrivesPerSet : len ( args ) ,
Endpoints : endpoints ,
} }
}
func mustGetNewEndpoints ( args ... string ) ( endpoints Endpoints ) {
endpoints , err := NewEndpoints ( args ... )
logger . FatalIf ( err , "unable to create new endpoint list" )
2017-04-12 00:44:27 +02:00
return endpoints
}
2019-11-20 02:42:27 +01:00
func getEndpointsLocalAddr ( endpointZones EndpointZones ) string {
for _ , endpoints := range endpointZones {
for _ , endpoint := range endpoints . Endpoints {
if endpoint . IsLocal && endpoint . Type ( ) == URLEndpointType {
return endpoint . Host
}
2017-04-12 00:44:27 +02:00
}
}
2018-12-14 08:37:46 +01:00
return net . JoinHostPort ( globalMinioHost , globalMinioPort )
2017-04-12 00:44:27 +02:00
}
2017-05-03 08:54:22 +02: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-10 01:41:35 +02: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 20:07:43 +01:00
ctx := context . Background ( )
2019-02-12 10:25:52 +01: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-10 01:41:35 +02:00
}
}
2018-09-21 04:22:09 +02: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 ,
partSizes [ ] int64 , metadata map [ string ] string , asMultipart bool ) {
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 )
req , err := newTestSignedRequestV4 ( "PUT" , getPutObjectURL ( "" , bucketName , objectName ) ,
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
reqI , err := newTestSignedRequestV4 ( "POST" , getNewMultipartURL ( "" , bucketName , objectName ) ,
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
req , errP := newTestSignedRequestV4 ( "PUT" ,
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 )
2019-04-09 01:54:46 +02:00
etag := rec . Header ( ) [ "ETag" ] [ 0 ]
2018-09-21 04:22:09 +02:00
if etag == "" {
t . Fatalf ( "Unexpected empty etag" )
}
cp = append ( cp , CompletePart { partID , etag [ 1 : len ( etag ) - 1 ] } )
}
// Call CompleteMultipart API
compMpBody , err := xml . Marshal ( CompleteMultipartUpload { Parts : cp } )
if err != nil {
t . Fatalf ( "Unexpected err: %#v" , err )
}
reqC , errP := newTestSignedRequestV4 ( "POST" ,
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 )
}
}