mirror of
https://github.com/minio/minio.git
synced 2024-12-23 21:55:53 -05:00
server: Add more elaborate startup messages. (#2731)
These messages based on our prep stage during XL and prints more informative message regarding drive information. This change also does a much needed refactoring.
This commit is contained in:
parent
63a7ca1af0
commit
6494b77d41
@ -645,7 +645,3 @@ func getAPIErrorResponse(err APIError, resource string) APIErrorResponse {
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func getErrMalformedCredentialDate(malformedDateStr string) {
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,12 @@ type objectAPIHandlers struct {
|
||||
}
|
||||
|
||||
// registerAPIRouter - registers S3 compatible APIs.
|
||||
func registerAPIRouter(mux *router.Router, api objectAPIHandlers) {
|
||||
func registerAPIRouter(mux *router.Router) {
|
||||
// Initialize API.
|
||||
api := objectAPIHandlers{
|
||||
ObjectAPI: newObjectLayerFn,
|
||||
}
|
||||
|
||||
// API Router
|
||||
apiRouter := mux.NewRoute().PathPrefix("/").Subrouter()
|
||||
|
||||
|
@ -35,7 +35,7 @@ func prepareBenchmarkBackend(instanceType string) (ObjectLayer, []string, error)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
obj, err := makeTestBackend(disks, instanceType)
|
||||
obj, _, err := initObjectLayer(disks, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -157,19 +156,6 @@ func TestSendBucketNotification(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func initMockEventNotifier(objAPI ObjectLayer) error {
|
||||
if objAPI == nil {
|
||||
return errInvalidArgument
|
||||
}
|
||||
globalEventNotifier = &eventNotifier{
|
||||
rwMutex: &sync.RWMutex{},
|
||||
queueTargets: nil,
|
||||
notificationConfigs: make(map[string]*notificationConfig),
|
||||
snsTargets: make(map[string][]chan []NotificationEvent),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
||||
// get random bucket name.
|
||||
randBucket := getRandomBucketName()
|
||||
@ -196,8 +182,8 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
|
||||
// Register the API end points with XL/FS object layer.
|
||||
apiRouter := initTestAPIEndPoints(obj, []string{
|
||||
"GetBucketNotificationHandler",
|
||||
"PutBucketNotificationHandler",
|
||||
"GetBucketNotification",
|
||||
"PutBucketNotification",
|
||||
})
|
||||
|
||||
// initialize the server and obtain the credentials and root.
|
||||
@ -212,7 +198,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
credentials := serverConfig.GetCredential()
|
||||
|
||||
//Initialize global event notifier with mock queue targets.
|
||||
err = initMockEventNotifier(obj)
|
||||
err = initEventNotifier(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to initialize mock event notifier %v",
|
||||
instanceType, err)
|
||||
@ -304,8 +290,8 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
|
||||
// Nil Object layer
|
||||
nilAPIRouter := initTestAPIEndPoints(nil, []string{
|
||||
"GetBucketNotificationHandler",
|
||||
"PutBucketNotificationHandler",
|
||||
"GetBucketNotification",
|
||||
"PutBucketNotification",
|
||||
})
|
||||
testRec := httptest.NewRecorder()
|
||||
testReq, tErr := newTestSignedRequestV4("GET", getGetBucketNotificationURL("", randBucket),
|
||||
@ -344,8 +330,8 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
|
||||
// Register the API end points with XL/FS object layer.
|
||||
apiRouter := initTestAPIEndPoints(obj, []string{
|
||||
"GetBucketNotificationHandler",
|
||||
"PutBucketNotificationHandler",
|
||||
"GetBucketNotification",
|
||||
"PutBucketNotification",
|
||||
})
|
||||
|
||||
// initialize the server and obtain the credentials and root.
|
||||
@ -360,7 +346,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
credentials := serverConfig.GetCredential()
|
||||
|
||||
//Initialize global event notifier with mock queue targets.
|
||||
err = initMockEventNotifier(obj)
|
||||
err = initEventNotifier(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to initialize mock event notifier %v",
|
||||
instanceType, err)
|
||||
@ -460,8 +446,8 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
|
||||
// Nil Object layer
|
||||
nilAPIRouter := initTestAPIEndPoints(nil, []string{
|
||||
"GetBucketNotificationHandler",
|
||||
"PutBucketNotificationHandler",
|
||||
"GetBucketNotification",
|
||||
"PutBucketNotification",
|
||||
})
|
||||
testRec := httptest.NewRecorder()
|
||||
testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket),
|
||||
@ -503,8 +489,8 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
|
||||
|
||||
// Register the API end points with XL/FS object layer.
|
||||
apiRouter := initTestAPIEndPoints(obj, []string{
|
||||
"PutBucketNotificationHandler",
|
||||
"ListenBucketNotificationHandler",
|
||||
"PutBucketNotification",
|
||||
"ListenBucketNotification",
|
||||
"PutObject",
|
||||
})
|
||||
|
||||
@ -519,8 +505,8 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
|
||||
|
||||
credentials := serverConfig.GetCredential()
|
||||
|
||||
//Initialize global event notifier with mock queue targets.
|
||||
err = initMockEventNotifier(obj)
|
||||
// Initialize global event notifier with mock queue targets.
|
||||
err = initEventNotifier(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to initialize mock event notifier %v",
|
||||
instanceType, err)
|
||||
@ -556,8 +542,8 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
|
||||
// FIXME: Need to find a way to run valid listen bucket notification test case without blocking the unit test.
|
||||
{randBucket, "", "", invalidEvents, CheckStatus, signatureMismatchError.HTTPStatusCode, ""},
|
||||
{randBucket, tooBigPrefix, "", validEvents, CheckStatus, http.StatusBadRequest, ""},
|
||||
{invalidBucket, "", "", nil, CheckStatus, http.StatusBadRequest, ""},
|
||||
{randBucket, "", "", nil, InvalidAuth, signatureMismatchError.HTTPStatusCode, signatureMismatchError.Code},
|
||||
{invalidBucket, "", "", validEvents, CheckStatus, http.StatusBadRequest, ""},
|
||||
{randBucket, "", "", validEvents, InvalidAuth, signatureMismatchError.HTTPStatusCode, signatureMismatchError.Code},
|
||||
}
|
||||
|
||||
for i, test := range testCases {
|
||||
@ -607,8 +593,8 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
|
||||
|
||||
// Nil Object layer
|
||||
nilAPIRouter := initTestAPIEndPoints(nil, []string{
|
||||
"PutBucketNotificationHandler",
|
||||
"ListenBucketNotificationHandler",
|
||||
"PutBucketNotification",
|
||||
"ListenBucketNotification",
|
||||
})
|
||||
testRec = httptest.NewRecorder()
|
||||
testReq, tErr = newTestSignedRequestV4("GET",
|
||||
@ -647,8 +633,8 @@ func testRemoveNotificationConfig(obj ObjectLayer, instanceType string, t TestEr
|
||||
|
||||
// Register the API end points with XL/FS object layer.
|
||||
apiRouter := initTestAPIEndPoints(obj, []string{
|
||||
"PutBucketNotificationHandler",
|
||||
"ListenBucketNotificationHandler",
|
||||
"PutBucketNotification",
|
||||
"ListenBucketNotification",
|
||||
})
|
||||
|
||||
// initialize the server and obtain the credentials and root.
|
||||
@ -663,7 +649,7 @@ func testRemoveNotificationConfig(obj ObjectLayer, instanceType string, t TestEr
|
||||
credentials := serverConfig.GetCredential()
|
||||
|
||||
//Initialize global event notifier with mock queue targets.
|
||||
err = initMockEventNotifier(obj)
|
||||
err = initEventNotifier(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to initialize mock event notifier %v",
|
||||
instanceType, err)
|
||||
|
@ -136,27 +136,6 @@ func isMinioSNS(topicARN arnTopic) bool {
|
||||
return strings.HasSuffix(topicARN.Type, snsTypeMinio)
|
||||
}
|
||||
|
||||
// isMinioSNSConfigured - verifies if one topic ARN is valid and is enabled.
|
||||
func isMinioSNSConfigured(topicARN string, topicConfigs []topicConfig) bool {
|
||||
for _, topicConfig := range topicConfigs {
|
||||
// Validate if topic ARN is already enabled.
|
||||
if topicARN == topicConfig.TopicARN {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate if we recognize the queue type.
|
||||
func isValidQueue(sqsARN arnSQS) bool {
|
||||
amqpQ := isAMQPQueue(sqsARN) // Is amqp queue?
|
||||
natsQ := isNATSQueue(sqsARN) // Is nats queue?
|
||||
elasticQ := isElasticQueue(sqsARN) // Is elastic queue?
|
||||
redisQ := isRedisQueue(sqsARN) // Is redis queue?
|
||||
postgresQ := isPostgreSQLQueue(sqsARN) // Is postgres queue?
|
||||
return amqpQ || natsQ || elasticQ || redisQ || postgresQ
|
||||
}
|
||||
|
||||
// Validate if we recognize the topic type.
|
||||
func isValidTopic(topicARN arnTopic) bool {
|
||||
return isMinioSNS(topicARN) // Is minio topic?.
|
||||
|
@ -83,15 +83,6 @@ func isConfigFileExists() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// mustGetConfigFile must get server config file.
|
||||
func mustGetConfigFile() string {
|
||||
configFile, err := getConfigFile()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return configFile
|
||||
}
|
||||
|
||||
// getConfigFile get server config file.
|
||||
func getConfigFile() (string, error) {
|
||||
configPath, err := getConfigPath()
|
||||
|
@ -59,14 +59,6 @@ func TestControlLockMain(t *testing.T) {
|
||||
// schedule cleanup at the end
|
||||
defer testServer.Stop()
|
||||
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
// set debug lock info to `nil` so that other tests do not see
|
||||
// such modified env settings.
|
||||
defer func() {
|
||||
nsMutex.debugLockMap = nil
|
||||
}()
|
||||
|
||||
// fetch http server endpoint
|
||||
url := testServer.Server.URL
|
||||
|
||||
@ -95,29 +87,13 @@ func TestControlShutdownMain(t *testing.T) {
|
||||
// fetch http server endpoint
|
||||
url := testServer.Server.URL
|
||||
|
||||
// create a dummy exit function
|
||||
testExitFn := func(exitCode int) {
|
||||
if exitCode != int(exitSuccess) {
|
||||
t.Errorf("Control-Shutdown-Main test failed - server exited with non-success error code - %d",
|
||||
exitCode)
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the shutdown signal listener
|
||||
err := initGracefulShutdown(testExitFn)
|
||||
if err != nil {
|
||||
t.Fatalf("Control-Shutdown-Main test failed in initGracefulShutdown() - %s",
|
||||
err.Error())
|
||||
}
|
||||
|
||||
// create args to call
|
||||
args := []string{"./minio", "control", "shutdown", url}
|
||||
|
||||
// run app
|
||||
err = app.Run(args)
|
||||
err := app.Run(args)
|
||||
if err != nil {
|
||||
t.Errorf("Control-Shutdown-Main test failed with - %s",
|
||||
err.Error())
|
||||
t.Errorf("Control-Shutdown-Main test failed with - %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +110,6 @@ func TestControlMain(t *testing.T) {
|
||||
// run app
|
||||
err := app.Run(args)
|
||||
if err != nil {
|
||||
t.Errorf("Control-Main test failed with - %s",
|
||||
err.Error())
|
||||
t.Errorf("Control-Main test failed with - %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -113,14 +113,10 @@ func (c *controllerAPIHandlers) HealObjectHandler(args *HealObjectArgs, reply *G
|
||||
|
||||
// HealObject - heal the object.
|
||||
func (c *controllerAPIHandlers) HealDiskMetadataHandler(args *GenericArgs, reply *GenericReply) error {
|
||||
objAPI := c.ObjectAPI()
|
||||
if objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
if !isRPCTokenValid(args.Token) {
|
||||
return errInvalidToken
|
||||
}
|
||||
err := objAPI.HealDiskMetadata()
|
||||
err := repairDiskMetadata(c.StorageDisks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -153,15 +149,6 @@ func (c *controllerAPIHandlers) ShutdownHandler(args *ShutdownArgs, reply *Gener
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *controllerAPIHandlers) TryInitHandler(args *GenericArgs, reply *GenericReply) error {
|
||||
go func() {
|
||||
globalWakeupCh <- struct{}{}
|
||||
}()
|
||||
*reply = GenericReply{}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// LockInfo - RPC control handler for `minio control lock`.
|
||||
// Returns the info of the locks held in the system.
|
||||
func (c *controllerAPIHandlers) LockInfo(arg *GenericArgs, reply *SystemLockState) error {
|
||||
|
@ -28,7 +28,13 @@ const (
|
||||
)
|
||||
|
||||
// Register controller RPC handlers.
|
||||
func registerControllerRPCRouter(mux *router.Router, ctrlHandlers *controllerAPIHandlers) {
|
||||
func registerControllerRPCRouter(mux *router.Router, srvCmdConfig serverCmdConfig) {
|
||||
// Initialize Controller.
|
||||
ctrlHandlers := &controllerAPIHandlers{
|
||||
ObjectAPI: newObjectLayerFn,
|
||||
StorageDisks: srvCmdConfig.storageDisks,
|
||||
}
|
||||
|
||||
ctrlRPCServer := rpc.NewServer()
|
||||
ctrlRPCServer.RegisterName("Controller", ctrlHandlers)
|
||||
|
||||
@ -38,5 +44,6 @@ func registerControllerRPCRouter(mux *router.Router, ctrlHandlers *controllerAPI
|
||||
|
||||
// Handler for object healing.
|
||||
type controllerAPIHandlers struct {
|
||||
ObjectAPI func() ObjectLayer
|
||||
ObjectAPI func() ObjectLayer
|
||||
StorageDisks []StorageAPI
|
||||
}
|
||||
|
@ -66,14 +66,6 @@ func TestRPCControlLock(t *testing.T) {
|
||||
|
||||
// Tests to validate the correctness of lock instrumentation control RPC end point.
|
||||
func (s *TestRPCControllerSuite) testRPCControlLock(c *testing.T) {
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
// set debug lock info to `nil` so that the next tests have to
|
||||
// initialize them again.
|
||||
defer func() {
|
||||
nsMutex.debugLockMap = nil
|
||||
}()
|
||||
|
||||
expectedResult := []lockStateCase{
|
||||
// Test case - 1.
|
||||
// Case where 10 read locks are held.
|
||||
@ -297,10 +289,8 @@ func (s *TestRPCControllerSuite) testControllerHealDiskMetadataH(c *testing.T) {
|
||||
args := &GenericArgs{}
|
||||
reply := &GenericReply{}
|
||||
err := client.Call("Controller.HealDiskMetadataHandler", args, reply)
|
||||
|
||||
if err != nil {
|
||||
c.Errorf("Control.HealDiskMetadataH - test failed with <ERROR> %s",
|
||||
err.Error())
|
||||
c.Errorf("Control.HealDiskMetadataH - test failed with <ERROR> %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,20 +310,16 @@ func (s *TestRPCControllerSuite) testControllerHealObjectH(t *testing.T) {
|
||||
client := newAuthClient(s.testAuthConf)
|
||||
defer client.Close()
|
||||
|
||||
err := s.testServer.Obj.MakeBucket("testbucket")
|
||||
err := newObjectLayerFn().MakeBucket("testbucket")
|
||||
if err != nil {
|
||||
t.Fatalf(
|
||||
"Controller.HealObjectH - create bucket failed with <ERROR> %s",
|
||||
err.Error(),
|
||||
)
|
||||
"Controller.HealObjectH - create bucket failed with <ERROR> %s", err)
|
||||
}
|
||||
|
||||
datum := strings.NewReader("a")
|
||||
_, err = s.testServer.Obj.PutObject("testbucket", "testobject", 1,
|
||||
datum, nil, "")
|
||||
_, err = newObjectLayerFn().PutObject("testbucket", "testobject", 1, datum, nil, "")
|
||||
if err != nil {
|
||||
t.Fatalf("Controller.HealObjectH - put object failed with <ERROR> %s",
|
||||
err.Error())
|
||||
t.Fatalf("Controller.HealObjectH - put object failed with <ERROR> %s", err)
|
||||
}
|
||||
|
||||
args := &HealObjectArgs{GenericArgs{}, "testbucket", "testobject"}
|
||||
@ -341,8 +327,7 @@ func (s *TestRPCControllerSuite) testControllerHealObjectH(t *testing.T) {
|
||||
err = client.Call("Controller.HealObjectHandler", args, reply)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Controller.HealObjectH - test failed with <ERROR> %s",
|
||||
err.Error())
|
||||
t.Errorf("Controller.HealObjectH - test failed with <ERROR> %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,20 +348,16 @@ func (s *TestRPCControllerSuite) testControllerListObjectsHealH(t *testing.T) {
|
||||
defer client.Close()
|
||||
|
||||
// careate a bucket
|
||||
err := s.testServer.Obj.MakeBucket("testbucket")
|
||||
err := newObjectLayerFn().MakeBucket("testbucket")
|
||||
if err != nil {
|
||||
t.Fatalf(
|
||||
"Controller.ListObjectsHealH - create bucket failed - %s",
|
||||
err.Error(),
|
||||
)
|
||||
"Controller.ListObjectsHealH - create bucket failed - %s", err)
|
||||
}
|
||||
|
||||
r := strings.NewReader("0")
|
||||
_, err = s.testServer.Obj.PutObject(
|
||||
"testbucket", "testObj-0", 1, r, nil, "")
|
||||
_, err = newObjectLayerFn().PutObject("testbucket", "testObj-0", 1, r, nil, "")
|
||||
if err != nil {
|
||||
t.Fatalf("Controller.ListObjectsHealH - object creation failed - %s",
|
||||
err.Error())
|
||||
t.Fatalf("Controller.ListObjectsHealH - object creation failed - %s", err)
|
||||
}
|
||||
|
||||
args := &HealListArgs{
|
||||
@ -387,7 +368,6 @@ func (s *TestRPCControllerSuite) testControllerListObjectsHealH(t *testing.T) {
|
||||
err = client.Call("Controller.ListObjectsHealHandler", args, reply)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Controller.ListObjectsHealHandler - test failed - %s",
|
||||
err.Error())
|
||||
t.Errorf("Controller.ListObjectsHealHandler - test failed - %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -222,8 +222,9 @@ func TestErasureReadUtils(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
objLayer, err := getXLObjectLayer(disks, nil)
|
||||
objLayer, _, err := initObjectLayer(disks, nil)
|
||||
if err != nil {
|
||||
removeRoots(disks)
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer removeRoots(disks)
|
||||
|
@ -90,29 +90,26 @@ func testEventNotify(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
||||
|
||||
// Tests various forms of inititalization of event notifier.
|
||||
func TestInitEventNotifier(t *testing.T) {
|
||||
disk, err := getRandomDisks(1)
|
||||
disks, err := getRandomDisks(1)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||
}
|
||||
fs, err := getSingleNodeObjectLayer(disk[0])
|
||||
defer removeRoots(disks)
|
||||
fs, _, err := initObjectLayer(disks, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
nDisks := 16
|
||||
disks, err := getRandomDisks(nDisks)
|
||||
disks, err = getRandomDisks(nDisks)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create directories for XL backend. ", err)
|
||||
}
|
||||
xl, err := getXLObjectLayer(disks, nil)
|
||||
defer removeRoots(disks)
|
||||
xl, _, err := initObjectLayer(disks, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to initialize XL backend.", err)
|
||||
}
|
||||
|
||||
disks = append(disks, disk...)
|
||||
for _, d := range disks {
|
||||
defer removeAll(d)
|
||||
}
|
||||
|
||||
// Collection of test cases for inititalizing event notifier.
|
||||
testCases := []struct {
|
||||
objAPI ObjectLayer
|
||||
@ -156,11 +153,11 @@ func TestInitEventNotifierFaultyDisks(t *testing.T) {
|
||||
defer removeAll(rootPath)
|
||||
|
||||
disk, err := getRandomDisks(1)
|
||||
defer removeAll(disk[0])
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||
}
|
||||
obj, err := getSingleNodeObjectLayer(disk[0])
|
||||
defer removeAll(disk[0])
|
||||
obj, _, err := initObjectLayer(disk, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
@ -210,7 +207,7 @@ func TestInitEventNotifierWithAMQP(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||
}
|
||||
fs, err := getSingleNodeObjectLayer(disk[0])
|
||||
fs, _, err := initObjectLayer(disk, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
@ -237,7 +234,7 @@ func TestInitEventNotifierWithElasticSearch(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||
}
|
||||
fs, err := getSingleNodeObjectLayer(disk[0])
|
||||
fs, _, err := initObjectLayer(disk, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
@ -264,7 +261,7 @@ func TestInitEventNotifierWithRedis(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||
}
|
||||
fs, err := getSingleNodeObjectLayer(disk[0])
|
||||
fs, _, err := initObjectLayer(disk, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
@ -295,7 +292,7 @@ func TestListenBucketNotification(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||
}
|
||||
obj, err := getSingleNodeObjectLayer(disk[0])
|
||||
obj, _, err := initObjectLayer(disk, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
@ -325,11 +322,6 @@ func TestListenBucketNotification(t *testing.T) {
|
||||
t.Fatal("Unexpected error:", err)
|
||||
}
|
||||
|
||||
// Validate if minio SNS is configured for an empty topic configs.
|
||||
if isMinioSNSConfigured(listenARN, nil) {
|
||||
t.Fatal("SNS listen shouldn't be configured.")
|
||||
}
|
||||
|
||||
// Check if the config is loaded
|
||||
notificationCfg := globalEventNotifier.GetBucketNotificationConfig(bucketName)
|
||||
if notificationCfg == nil {
|
||||
@ -339,8 +331,8 @@ func TestListenBucketNotification(t *testing.T) {
|
||||
t.Fatal("Notification config is not correctly loaded. Exactly one topic and one queue config are expected")
|
||||
}
|
||||
|
||||
// Check if listen notification config is enabled
|
||||
if !isMinioSNSConfigured(listenARN, notificationCfg.TopicConfigs) {
|
||||
// Check if topic ARN is enabled
|
||||
if notificationCfg.TopicConfigs[0].TopicARN != listenARN {
|
||||
t.Fatal("SNS listen is not configured.")
|
||||
}
|
||||
|
||||
|
@ -149,6 +149,20 @@ func reduceFormatErrs(errs []error, diskCount int) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// creates format.json, the FS format info in minioMetaBucket.
|
||||
func initFormatFS(storageDisk StorageAPI) error {
|
||||
// Initialize meta volume, if volume already exists ignores it.
|
||||
if err := initMetaVolume([]StorageAPI{storageDisk}); err != nil {
|
||||
return fmt.Errorf("Unable to initialize '.minio.sys' meta volume, %s", err)
|
||||
}
|
||||
return saveFSFormatData(storageDisk, newFSFormatV1())
|
||||
}
|
||||
|
||||
// loads format.json from minioMetaBucket if it exists.
|
||||
func loadFormatFS(storageDisk StorageAPI) (format *formatConfigV1, err error) {
|
||||
return loadFormat(storageDisk)
|
||||
}
|
||||
|
||||
// loadAllFormats - load all format config from all input disks in parallel.
|
||||
func loadAllFormats(bootstrapDisks []StorageAPI) ([]*formatConfigV1, []error) {
|
||||
// Initialize sync waitgroup.
|
||||
@ -198,6 +212,17 @@ func loadAllFormats(bootstrapDisks []StorageAPI) ([]*formatConfigV1, []error) {
|
||||
// if (jbod inconsistent) return error // phase2
|
||||
// if (disks not recognized) // Always error.
|
||||
func genericFormatCheck(formatConfigs []*formatConfigV1, sErrs []error) (err error) {
|
||||
if len(formatConfigs) == 1 {
|
||||
// Successfully read, validate further.
|
||||
if sErrs[0] == nil {
|
||||
if !isFSFormat(formatConfigs[0]) {
|
||||
return errFSDiskFormat
|
||||
}
|
||||
return nil
|
||||
} // Returns error here.
|
||||
return sErrs[0]
|
||||
}
|
||||
|
||||
// Calculate the errors.
|
||||
var (
|
||||
errCorruptFormatCount = 0
|
||||
@ -390,8 +415,7 @@ func loadFormat(disk StorageAPI) (format *formatConfigV1, err error) {
|
||||
return format, nil
|
||||
}
|
||||
|
||||
// isFormatNotFound - returns true if all `format.json` are not
|
||||
// found on all disks.
|
||||
// isFormatNotFound - returns true if all `format.json` are not found on all disks.
|
||||
func isFormatNotFound(formats []*formatConfigV1) bool {
|
||||
for _, format := range formats {
|
||||
// One of the `format.json` is found.
|
||||
@ -403,8 +427,7 @@ func isFormatNotFound(formats []*formatConfigV1) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// isFormatFound - returns true if all input formats are found on
|
||||
// all disks.
|
||||
// isFormatFound - returns true if all input formats are found on all disks.
|
||||
func isFormatFound(formats []*formatConfigV1) bool {
|
||||
for _, format := range formats {
|
||||
// One of `format.json` is not found.
|
||||
|
@ -114,6 +114,13 @@ func genFormatXLInvalidXLVersion() []*formatConfigV1 {
|
||||
return formatConfigs
|
||||
}
|
||||
|
||||
func genFormatFS() *formatConfigV1 {
|
||||
return &formatConfigV1{
|
||||
Version: "1",
|
||||
Format: "fs",
|
||||
}
|
||||
}
|
||||
|
||||
// generates a invalid format.json version for XL backend.
|
||||
func genFormatXLInvalidJBODCount() []*formatConfigV1 {
|
||||
jbod := make([]string, 7)
|
||||
@ -269,7 +276,7 @@ func TestFormatXLHealFreshDisks(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create an instance of xl backend.
|
||||
obj, err := getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err := initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@ -301,7 +308,7 @@ func TestFormatXLHealFreshDisksErrorExpected(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create an instance of xl backend.
|
||||
obj, err := getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err := initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@ -311,10 +318,8 @@ func TestFormatXLHealFreshDisksErrorExpected(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < 16; i++ {
|
||||
d := storageDisks[i].(*posix)
|
||||
storageDisks[i] = &naughtyDisk{disk: d, defaultErr: errDiskNotFound}
|
||||
}
|
||||
// Prepares all disks are offline.
|
||||
prepareNOfflineDisks(storageDisks, 16, t)
|
||||
|
||||
// Load again XL format.json to validate it
|
||||
_, err = loadFormatXL(storageDisks)
|
||||
@ -586,8 +591,9 @@ func TestInitFormatXLErrors(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer removeRoots(fsDirs)
|
||||
// Create an instance of xl backend.
|
||||
obj, err := getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err := initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -620,8 +626,6 @@ func TestInitFormatXLErrors(t *testing.T) {
|
||||
if err := initFormatXL(testStorageDisks); err != errDiskNotFound {
|
||||
t.Fatal("Got a different error: ", err)
|
||||
}
|
||||
|
||||
removeRoots(fsDirs)
|
||||
}
|
||||
|
||||
// Test for reduceFormatErrs()
|
||||
@ -672,7 +676,14 @@ func TestGenericFormatCheck(t *testing.T) {
|
||||
if err := genericFormatCheck(formatConfigs, errs); err == nil {
|
||||
t.Fatalf("Should fail here")
|
||||
}
|
||||
|
||||
errs = []error{nil}
|
||||
if err := genericFormatCheck([]*formatConfigV1{genFormatFS()}, errs); err != nil {
|
||||
t.Fatal("Got unexpected err: ", err)
|
||||
}
|
||||
errs = []error{errFaultyDisk}
|
||||
if err := genericFormatCheck([]*formatConfigV1{genFormatFS()}, errs); err == nil {
|
||||
t.Fatalf("Should fail here")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFormatXLErrs(t *testing.T) {
|
||||
@ -681,9 +692,10 @@ func TestLoadFormatXLErrs(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer removeRoots(fsDirs)
|
||||
|
||||
// Create an instance of xl backend.
|
||||
obj, err := getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err := initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -707,8 +719,9 @@ func TestLoadFormatXLErrs(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer removeRoots(fsDirs)
|
||||
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -726,14 +739,13 @@ func TestLoadFormatXLErrs(t *testing.T) {
|
||||
t.Fatal("Got an unexpected error: ", err)
|
||||
}
|
||||
|
||||
removeRoots(fsDirs)
|
||||
|
||||
fsDirs, err = getRandomDisks(nDisks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer removeRoots(fsDirs)
|
||||
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -749,14 +761,13 @@ func TestLoadFormatXLErrs(t *testing.T) {
|
||||
t.Fatal("Got an unexpected error: ", err)
|
||||
}
|
||||
|
||||
removeRoots(fsDirs)
|
||||
|
||||
fsDirs, err = getRandomDisks(nDisks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer removeRoots(fsDirs)
|
||||
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -769,8 +780,6 @@ func TestLoadFormatXLErrs(t *testing.T) {
|
||||
if _, err := loadFormatXL(xl.storageDisks); err != errDiskNotFound {
|
||||
t.Fatal("Got an unexpected error: ", err)
|
||||
}
|
||||
|
||||
removeRoots(fsDirs)
|
||||
}
|
||||
|
||||
// Tests for healFormatXLCorruptedDisks() with cases which lead to errors
|
||||
@ -782,7 +791,7 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Everything is fine, should return nil
|
||||
obj, err := getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err := initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -799,7 +808,7 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Disks 0..15 are nil
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -818,7 +827,7 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// One disk returns Faulty Disk
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -839,7 +848,7 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// One disk is not found, heal corrupted disks should return nil
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -856,7 +865,7 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Remove format.json of all disks
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -877,7 +886,7 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Corrupted format json in one disk
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -902,7 +911,7 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Everything is fine, should return nil
|
||||
obj, err := getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err := initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -918,7 +927,7 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Disks 0..15 are nil
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -937,7 +946,7 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// One disk returns Faulty Disk
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -958,7 +967,7 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// One disk is not found, heal corrupted disks should return nil
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -975,7 +984,7 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Remove format.json of all disks
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -996,7 +1005,7 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Remove format.json of all disks
|
||||
obj, err = getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err = initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -123,8 +123,8 @@ func newFSMetaV1() (fsMeta fsMetaV1) {
|
||||
}
|
||||
|
||||
// newFSFormatV1 - initializes new formatConfigV1 with FS format info.
|
||||
func newFSFormatV1() (format formatConfigV1) {
|
||||
return formatConfigV1{
|
||||
func newFSFormatV1() (format *formatConfigV1) {
|
||||
return &formatConfigV1{
|
||||
Version: "1",
|
||||
Format: "fs",
|
||||
FS: &fsFormat{
|
||||
@ -134,12 +134,12 @@ func newFSFormatV1() (format formatConfigV1) {
|
||||
}
|
||||
|
||||
// isFSFormat - returns whether given formatConfigV1 is FS type or not.
|
||||
func isFSFormat(format formatConfigV1) bool {
|
||||
func isFSFormat(format *formatConfigV1) bool {
|
||||
return format.Format == "fs"
|
||||
}
|
||||
|
||||
// writes FS format (format.json) into minioMetaBucket.
|
||||
func writeFSFormatData(storage StorageAPI, fsFormat formatConfigV1) error {
|
||||
func saveFSFormatData(storage StorageAPI, fsFormat *formatConfigV1) error {
|
||||
metadataBytes, err := json.Marshal(fsFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -67,24 +67,31 @@ func TestHasExtendedHeader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func initFSObjects(disk string, t *testing.T) (obj ObjectLayer) {
|
||||
obj, _, err := initObjectLayer([]string{disk}, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// TestReadFsMetadata - readFSMetadata testing with a healthy and faulty disk
|
||||
func TestReadFSMetadata(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
|
||||
fs := obj.(fsObjects)
|
||||
|
||||
bucketName := "bucket"
|
||||
objectName := "object"
|
||||
|
||||
if err = obj.MakeBucket(bucketName); err != nil {
|
||||
if err := obj.MakeBucket(bucketName); err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
sha256sum := ""
|
||||
if _, err = obj.PutObject(bucketName, objectName, int64(len("abcd")), bytes.NewReader([]byte("abcd")),
|
||||
if _, err := obj.PutObject(bucketName, objectName, int64(len("abcd")), bytes.NewReader([]byte("abcd")),
|
||||
map[string]string{"X-Amz-Meta-AppId": "a"}, sha256sum); err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
@ -93,15 +100,15 @@ func TestReadFSMetadata(t *testing.T) {
|
||||
fsPath := "buckets/" + bucketName + "/" + objectName + "/fs.json"
|
||||
|
||||
// Regular fs metadata reading, no errors expected
|
||||
if _, err = readFSMetadata(fs.storage, ".minio.sys", fsPath); err != nil {
|
||||
if _, err := readFSMetadata(fs.storage, ".minio.sys", fsPath); err != nil {
|
||||
t.Fatal("Unexpected error ", err)
|
||||
}
|
||||
|
||||
// Corrupted fs.json
|
||||
if err = fs.storage.AppendFile(".minio.sys", fsPath, []byte{'a'}); err != nil {
|
||||
if err := fs.storage.AppendFile(".minio.sys", fsPath, []byte{'a'}); err != nil {
|
||||
t.Fatal("Unexpected error ", err)
|
||||
}
|
||||
if _, err = readFSMetadata(fs.storage, ".minio.sys", fsPath); err == nil {
|
||||
if _, err := readFSMetadata(fs.storage, ".minio.sys", fsPath); err == nil {
|
||||
t.Fatal("Should fail", err)
|
||||
}
|
||||
|
||||
@ -109,7 +116,7 @@ func TestReadFSMetadata(t *testing.T) {
|
||||
fsStorage := fs.storage.(*posix)
|
||||
naughty := newNaughtyDisk(fsStorage, nil, errFaultyDisk)
|
||||
fs.storage = naughty
|
||||
if _, err = readFSMetadata(fs.storage, ".minio.sys", fsPath); errorCause(err) != errFaultyDisk {
|
||||
if _, err := readFSMetadata(fs.storage, ".minio.sys", fsPath); errorCause(err) != errFaultyDisk {
|
||||
t.Fatal("Should fail", err)
|
||||
}
|
||||
|
||||
@ -119,20 +126,17 @@ func TestReadFSMetadata(t *testing.T) {
|
||||
func TestWriteFSMetadata(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
|
||||
bucketName := "bucket"
|
||||
objectName := "object"
|
||||
|
||||
if err = obj.MakeBucket(bucketName); err != nil {
|
||||
if err := obj.MakeBucket(bucketName); err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
sha256sum := ""
|
||||
if _, err = obj.PutObject(bucketName, objectName, int64(len("abcd")), bytes.NewReader([]byte("abcd")),
|
||||
if _, err := obj.PutObject(bucketName, objectName, int64(len("abcd")), bytes.NewReader([]byte("abcd")),
|
||||
map[string]string{"X-Amz-Meta-AppId": "a"}, sha256sum); err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
|
@ -28,11 +28,8 @@ func TestFSIsBucketExist(t *testing.T) {
|
||||
// Prepare for testing
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
bucketName := "bucket"
|
||||
|
||||
@ -64,22 +61,20 @@ func TestFSIsUploadExists(t *testing.T) {
|
||||
// Prepare for testing
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
|
||||
var uploadID string
|
||||
bucketName := "bucket"
|
||||
objectName := "object"
|
||||
|
||||
obj.MakeBucket(bucketName)
|
||||
uploadID, err = obj.NewMultipartUpload(bucketName, objectName, nil)
|
||||
if err := obj.MakeBucket(bucketName); err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
|
||||
uploadID, err := obj.NewMultipartUpload(bucketName, objectName, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
// Test with valid upload id
|
||||
if exists := fs.isUploadIDExists(bucketName, objectName, uploadID); !exists {
|
||||
@ -110,10 +105,8 @@ func TestFSWriteUploadJSON(t *testing.T) {
|
||||
// Prepare for tests
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
|
||||
bucketName := "bucket"
|
||||
@ -121,6 +114,9 @@ func TestFSWriteUploadJSON(t *testing.T) {
|
||||
|
||||
obj.MakeBucket(bucketName)
|
||||
uploadID, err := obj.NewMultipartUpload(bucketName, objectName, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
@ -146,10 +142,8 @@ func TestFSUpdateUploadsJSON(t *testing.T) {
|
||||
// Prepare for tests
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
|
||||
bucketName := "bucket"
|
||||
|
@ -462,22 +462,11 @@ func (fs fsObjects) PutObjectPart(bucket, object, uploadID string, partID int, s
|
||||
return "", traceError(IncompleteBody{})
|
||||
}
|
||||
|
||||
// Validate if payload is valid.
|
||||
if isSignVerify(data) {
|
||||
if err := data.(*signVerifyReader).Verify(); err != nil {
|
||||
// Incoming payload wrong, delete the temporary object.
|
||||
fs.storage.DeleteFile(minioMetaBucket, tmpPartPath)
|
||||
// Error return.
|
||||
return "", toObjectErr(traceError(err), bucket, object)
|
||||
}
|
||||
}
|
||||
|
||||
newMD5Hex := hex.EncodeToString(md5Writer.Sum(nil))
|
||||
if md5Hex != "" {
|
||||
if newMD5Hex != md5Hex {
|
||||
// MD5 mismatch, delete the temporary object.
|
||||
fs.storage.DeleteFile(minioMetaBucket, tmpPartPath)
|
||||
|
||||
return "", traceError(BadDigest{md5Hex, newMD5Hex})
|
||||
}
|
||||
}
|
||||
|
@ -31,10 +31,7 @@ func TestNewMultipartUploadFaultyDisk(t *testing.T) {
|
||||
// Prepare for tests
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
obj := initFSObjects(disk, t)
|
||||
|
||||
fs := obj.(fsObjects)
|
||||
bucketName := "bucket"
|
||||
@ -67,18 +64,14 @@ func TestPutObjectPartFaultyDisk(t *testing.T) {
|
||||
// Prepare for tests
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
bucketName := "bucket"
|
||||
objectName := "object"
|
||||
data := []byte("12345")
|
||||
dataLen := int64(len(data))
|
||||
|
||||
if err = obj.MakeBucket(bucketName); err != nil {
|
||||
if err := obj.MakeBucket(bucketName); err != nil {
|
||||
t.Fatal("Cannot create bucket, err: ", err)
|
||||
}
|
||||
|
||||
@ -119,17 +112,14 @@ func TestCompleteMultipartUploadFaultyDisk(t *testing.T) {
|
||||
// Prepare for tests
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
obj := initFSObjects(disk, t)
|
||||
|
||||
fs := obj.(fsObjects)
|
||||
bucketName := "bucket"
|
||||
objectName := "object"
|
||||
data := []byte("12345")
|
||||
|
||||
if err = obj.MakeBucket(bucketName); err != nil {
|
||||
if err := obj.MakeBucket(bucketName); err != nil {
|
||||
t.Fatal("Cannot create bucket, err: ", err)
|
||||
}
|
||||
|
||||
@ -175,17 +165,13 @@ func TestListMultipartUploadsFaultyDisk(t *testing.T) {
|
||||
// Prepare for tests
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
bucketName := "bucket"
|
||||
objectName := "object"
|
||||
data := []byte("12345")
|
||||
|
||||
if err = obj.MakeBucket(bucketName); err != nil {
|
||||
if err := obj.MakeBucket(bucketName); err != nil {
|
||||
t.Fatal("Cannot create bucket, err: ", err)
|
||||
}
|
||||
|
||||
|
88
cmd/fs-v1.go
88
cmd/fs-v1.go
@ -20,7 +20,7 @@ import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
@ -33,8 +33,7 @@ import (
|
||||
|
||||
// fsObjects - Implements fs object layer.
|
||||
type fsObjects struct {
|
||||
storage StorageAPI
|
||||
physicalDisk string
|
||||
storage StorageAPI
|
||||
|
||||
// List pool management.
|
||||
listPool *treeWalkPool
|
||||
@ -46,73 +45,27 @@ var fsTreeWalkIgnoredErrs = []error{
|
||||
errVolumeNotFound,
|
||||
}
|
||||
|
||||
// creates format.json, the FS format info in minioMetaBucket.
|
||||
func initFormatFS(storageDisk StorageAPI) error {
|
||||
return writeFSFormatData(storageDisk, newFSFormatV1())
|
||||
}
|
||||
|
||||
// loads format.json from minioMetaBucket if it exists.
|
||||
func loadFormatFS(storageDisk StorageAPI) (format formatConfigV1, err error) {
|
||||
// Reads entire `format.json`.
|
||||
buf, err := storageDisk.ReadAll(minioMetaBucket, fsFormatJSONFile)
|
||||
if err != nil {
|
||||
return formatConfigV1{}, err
|
||||
}
|
||||
|
||||
// Unmarshal format config.
|
||||
if err = json.Unmarshal(buf, &format); err != nil {
|
||||
return formatConfigV1{}, err
|
||||
}
|
||||
|
||||
// Return structured `format.json`.
|
||||
return format, nil
|
||||
}
|
||||
|
||||
// newFSObjects - initialize new fs object layer.
|
||||
func newFSObjects(disk string) (ObjectLayer, error) {
|
||||
storage, err := newStorageAPI(disk)
|
||||
if err != nil && err != errDiskNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Attempt to create `.minio.sys`.
|
||||
err = storage.MakeVol(minioMetaBucket)
|
||||
if err != nil {
|
||||
switch err {
|
||||
// Ignore the errors.
|
||||
case errVolumeExists, errDiskNotFound, errFaultyDisk:
|
||||
default:
|
||||
return nil, toObjectErr(err, minioMetaBucket)
|
||||
}
|
||||
func newFSObjects(storage StorageAPI) (ObjectLayer, error) {
|
||||
if storage == nil {
|
||||
return nil, errInvalidArgument
|
||||
}
|
||||
|
||||
// Runs house keeping code, like creating minioMetaBucket, cleaning up tmp files etc.
|
||||
if err = fsHouseKeeping(storage); err != nil {
|
||||
if err := fsHouseKeeping(storage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// loading format.json from minioMetaBucket.
|
||||
// Note: The format.json content is ignored, reserved for future use.
|
||||
format, err := loadFormatFS(storage)
|
||||
// Load format and validate.
|
||||
_, err := loadFormatFS(storage)
|
||||
if err != nil {
|
||||
if err == errFileNotFound {
|
||||
// format.json doesn't exist, create it inside minioMetaBucket.
|
||||
err = initFormatFS(storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else if !isFSFormat(format) {
|
||||
return nil, errFSDiskFormat
|
||||
return nil, fmt.Errorf("Unable to recognize backend format, %s", err)
|
||||
}
|
||||
|
||||
// Initialize fs objects.
|
||||
fs := fsObjects{
|
||||
storage: storage,
|
||||
physicalDisk: disk,
|
||||
listPool: newTreeWalkPool(globalLookupTimeout),
|
||||
storage: storage,
|
||||
listPool: newTreeWalkPool(globalLookupTimeout),
|
||||
}
|
||||
|
||||
// Return successfully initialized object layer.
|
||||
@ -153,10 +106,12 @@ func (fs fsObjects) Shutdown() error {
|
||||
func (fs fsObjects) StorageInfo() StorageInfo {
|
||||
info, err := fs.storage.DiskInfo()
|
||||
errorIf(err, "Unable to get disk info %#v", fs.storage)
|
||||
return StorageInfo{
|
||||
storageInfo := StorageInfo{
|
||||
Total: info.Total,
|
||||
Free: info.Free,
|
||||
}
|
||||
storageInfo.Backend.Type = FS
|
||||
return storageInfo
|
||||
}
|
||||
|
||||
/// Bucket operations
|
||||
@ -450,16 +405,6 @@ func (fs fsObjects) PutObject(bucket string, object string, size int64, data io.
|
||||
metadata["md5Sum"] = newMD5Hex
|
||||
}
|
||||
|
||||
// Validate if payload is valid.
|
||||
if isSignVerify(data) {
|
||||
if vErr := data.(*signVerifyReader).Verify(); vErr != nil {
|
||||
// Incoming payload wrong, delete the temporary object.
|
||||
fs.storage.DeleteFile(minioMetaBucket, tempObj)
|
||||
// Error return.
|
||||
return ObjectInfo{}, toObjectErr(traceError(vErr), bucket, object)
|
||||
}
|
||||
}
|
||||
|
||||
// md5Hex representation.
|
||||
md5Hex := metadata["md5Sum"]
|
||||
if md5Hex != "" {
|
||||
@ -679,8 +624,3 @@ func (fs fsObjects) HealObject(bucket, object string) error {
|
||||
func (fs fsObjects) ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
||||
return ListObjectsInfo{}, traceError(NotImplemented{})
|
||||
}
|
||||
|
||||
// HealDiskMetadata -- heal disk metadata, not supported in FS
|
||||
func (fs fsObjects) HealDiskMetadata() error {
|
||||
return NotImplemented{}
|
||||
}
|
||||
|
@ -40,27 +40,48 @@ func TestNewFS(t *testing.T) {
|
||||
disks = append(disks, xlDisk)
|
||||
}
|
||||
|
||||
fsStorageDisks, err := initStorageDisks([]string{disk}, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Uexpected error: ", err)
|
||||
}
|
||||
|
||||
xlStorageDisks, err := initStorageDisks(disks, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Uexpected error: ", err)
|
||||
}
|
||||
|
||||
// Initializes all disks with XL
|
||||
err := formatDisks(disks, nil)
|
||||
err = waitForFormatDisks(true, "", xlStorageDisks)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to format XL %s", err)
|
||||
}
|
||||
_, err = newXLObjects(disks, nil)
|
||||
_, err = newXLObjects(xlStorageDisks)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize XL object, %s", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
disk string
|
||||
disk StorageAPI
|
||||
expectedErr error
|
||||
}{
|
||||
{disk, nil},
|
||||
{disks[0], errFSDiskFormat},
|
||||
{fsStorageDisks[0], nil},
|
||||
{xlStorageDisks[0], errFSDiskFormat},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
if _, err := newFSObjects(testCase.disk); err != testCase.expectedErr {
|
||||
t.Fatalf("expected: %s, got: %s", testCase.expectedErr, err)
|
||||
if err = waitForFormatDisks(true, "", []StorageAPI{testCase.disk}); err != testCase.expectedErr {
|
||||
t.Errorf("expected: %s, got :%s", testCase.expectedErr, err)
|
||||
}
|
||||
}
|
||||
_, err = newFSObjects(nil)
|
||||
if err != errInvalidArgument {
|
||||
t.Errorf("Expecting error invalid argument, got %s", err)
|
||||
}
|
||||
_, err = newFSObjects(xlStorageDisks[0])
|
||||
if err != nil {
|
||||
errMsg := "Unable to recognize backend format, Disk is not in FS format."
|
||||
if err.Error() == errMsg {
|
||||
t.Errorf("Expecting %s, got %s", errMsg, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,10 +92,7 @@ func TestFSShutdown(t *testing.T) {
|
||||
// Prepare for tests
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
obj := initFSObjects(disk, t)
|
||||
|
||||
fs := obj.(fsObjects)
|
||||
fsStorage := fs.storage.(*posix)
|
||||
@ -108,15 +126,11 @@ func TestFSLoadFormatFS(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Should not fail here", err)
|
||||
}
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
|
||||
// Regular format loading
|
||||
_, err = loadFormatFS(fs.storage)
|
||||
_, err := loadFormatFS(fs.storage)
|
||||
if err != nil {
|
||||
t.Fatal("Should not fail here", err)
|
||||
}
|
||||
@ -141,11 +155,7 @@ func TestFSGetBucketInfo(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
bucketName := "bucket"
|
||||
|
||||
@ -182,7 +192,7 @@ func TestFSDeleteObject(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
|
||||
obj, _ := newFSObjects(disk)
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
bucketName := "bucket"
|
||||
objectName := "object"
|
||||
@ -223,7 +233,7 @@ func TestFSDeleteBucket(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
|
||||
obj, _ := newFSObjects(disk)
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
bucketName := "bucket"
|
||||
|
||||
@ -264,11 +274,10 @@ func TestFSListBuckets(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
|
||||
obj, _ := newFSObjects(disk)
|
||||
obj := initFSObjects(disk, t)
|
||||
fs := obj.(fsObjects)
|
||||
|
||||
bucketName := "bucket"
|
||||
|
||||
if err := obj.MakeBucket(bucketName); err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
@ -303,11 +312,8 @@ func TestFSHealObject(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
err = obj.HealObject("bucket", "object")
|
||||
obj := initFSObjects(disk, t)
|
||||
err := obj.HealObject("bucket", "object")
|
||||
if err == nil || !isSameType(errorCause(err), NotImplemented{}) {
|
||||
t.Fatalf("Heal Object should return NotImplemented error ")
|
||||
}
|
||||
@ -318,26 +324,8 @@ func TestFSListObjectsHeal(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
_, err = obj.ListObjectsHeal("bucket", "prefix", "marker", "delimiter", 1000)
|
||||
if err == nil || !isSameType(errorCause(err), NotImplemented{}) {
|
||||
t.Fatalf("Heal Object should return NotImplemented error ")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFSHealDiskMetadata - tests for fs HealDiskMetadata
|
||||
func TestFSHealDiskMetadata(t *testing.T) {
|
||||
disk := filepath.Join(os.TempDir(), "minio-"+nextSuffix())
|
||||
defer removeAll(disk)
|
||||
|
||||
obj, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot create a new FS object: ", err)
|
||||
}
|
||||
err = obj.HealDiskMetadata()
|
||||
obj := initFSObjects(disk, t)
|
||||
_, err := obj.ListObjectsHeal("bucket", "prefix", "marker", "delimiter", 1000)
|
||||
if err == nil || !isSameType(errorCause(err), NotImplemented{}) {
|
||||
t.Fatalf("Heal Object should return NotImplemented error ")
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ var (
|
||||
globalMaxCacheSize = uint64(maxCacheSize)
|
||||
// Cache expiry.
|
||||
globalCacheExpiry = objcache.DefaultExpiry
|
||||
// Minio default port, can be changed through command line.
|
||||
globalMinioPort = 9000
|
||||
// Add new variable global values here.
|
||||
)
|
||||
|
||||
|
@ -22,7 +22,6 @@ import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Validates location constraint in PutBucket request body.
|
||||
@ -124,16 +123,3 @@ func extractPostPolicyFormValues(reader *multipart.Reader) (filePart io.Reader,
|
||||
}
|
||||
return filePart, fileName, formValues, nil
|
||||
}
|
||||
|
||||
// Send whitespace character, once every 5secs, until CompleteMultipartUpload is done.
|
||||
// CompleteMultipartUpload method of the object layer indicates that it's done via doneCh
|
||||
func sendWhiteSpaceChars(w http.ResponseWriter, doneCh <-chan struct{}) {
|
||||
for {
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
w.Write([]byte(" "))
|
||||
case <-doneCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,16 +107,13 @@ func (l LockInfoStateNotBlocked) Error() string {
|
||||
return fmt.Sprintf("Lock state should be \"Blocked\" for <volume> %s, <path> %s, <operationID> %s.", l.volume, l.path, l.operationID)
|
||||
}
|
||||
|
||||
var errLockNotInitialized = errors.New("Debug Lock Map not initialized:\n1. Enable Lock Debugging using right ENV settings \n2. Make sure initNSLock() is called.")
|
||||
var errLockNotInitialized = errors.New("Debug lockMap not initialized.")
|
||||
|
||||
// change the state of the lock from Blocked to Running.
|
||||
func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationID string, readLock bool) error {
|
||||
// This operation is not executed under the scope nsLockMap.mutex.Lock(), lock has to be explicitly held here.
|
||||
n.lockMapMutex.Lock()
|
||||
defer n.lockMapMutex.Unlock()
|
||||
if n.debugLockMap == nil {
|
||||
return errLockNotInitialized
|
||||
}
|
||||
// new state info to be set for the lock.
|
||||
newLockInfo := debugLockInfo{
|
||||
lockOrigin: lockOrigin,
|
||||
@ -132,38 +129,32 @@ func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationI
|
||||
}
|
||||
|
||||
// check whether the lock info entry for <volume, path> pair already exists and its not `nil`.
|
||||
if debugLockMap, ok := n.debugLockMap[param]; ok {
|
||||
// ``*debugLockInfoPerVolumePath` entry containing lock info for `param <volume, path>` is `nil`.
|
||||
if debugLockMap == nil {
|
||||
return errLockNotInitialized
|
||||
}
|
||||
} else {
|
||||
// The lock state info foe given <volume, path> pair should already exist.
|
||||
lockInfo, ok := n.debugLockMap[param]
|
||||
if !ok {
|
||||
// The lock state info for given <volume, path> pair should already exist.
|
||||
// If not return `LockInfoVolPathMssing`.
|
||||
return LockInfoVolPathMssing{param.volume, param.path}
|
||||
}
|
||||
|
||||
// Lock info the for the given operation ID shouldn't be `nil`.
|
||||
if n.debugLockMap[param].lockInfo == nil {
|
||||
return LockInfoOpsIDNotFound{param.volume, param.path, operationID}
|
||||
if lockInfo == nil {
|
||||
return errLockNotInitialized
|
||||
}
|
||||
|
||||
if lockInfo, ok := n.debugLockMap[param].lockInfo[operationID]; ok {
|
||||
// The entry for the lock origined at `lockOrigin` should already exist.
|
||||
// If not return `LockInfoOriginNotFound`.
|
||||
if lockInfo.lockOrigin != lockOrigin {
|
||||
return LockInfoOriginNotFound{param.volume, param.path, operationID, lockOrigin}
|
||||
}
|
||||
// Status of the lock should already be set to "Blocked".
|
||||
// If not return `LockInfoStateNotBlocked`.
|
||||
if lockInfo.status != "Blocked" {
|
||||
return LockInfoStateNotBlocked{param.volume, param.path, operationID}
|
||||
}
|
||||
} else {
|
||||
lockInfoOpID, ok := n.debugLockMap[param].lockInfo[operationID]
|
||||
if !ok {
|
||||
// The lock info entry for given `opsID` should already exist for given <volume, path> pair.
|
||||
// If not return `LockInfoOpsIDNotFound`.
|
||||
return LockInfoOpsIDNotFound{param.volume, param.path, operationID}
|
||||
}
|
||||
// The entry for the lock origined at `lockOrigin` should already exist.
|
||||
// If not return `LockInfoOriginNotFound`.
|
||||
if lockInfoOpID.lockOrigin != lockOrigin {
|
||||
return LockInfoOriginNotFound{param.volume, param.path, operationID, lockOrigin}
|
||||
}
|
||||
// Status of the lock should already be set to "Blocked".
|
||||
// If not return `LockInfoStateNotBlocked`.
|
||||
if lockInfoOpID.status != "Blocked" {
|
||||
return LockInfoStateNotBlocked{param.volume, param.path, operationID}
|
||||
}
|
||||
|
||||
// All checks finished.
|
||||
// changing the status of the operation from blocked to running and updating the time.
|
||||
@ -178,12 +169,12 @@ func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nsLockMap) initLockInfoForVolumePath(param nsParam) {
|
||||
n.debugLockMap[param] = newDebugLockInfoPerVolumePath()
|
||||
}
|
||||
|
||||
// change the state of the lock from Ready to Blocked.
|
||||
func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockOrigin, operationID string, readLock bool) error {
|
||||
if n.debugLockMap == nil {
|
||||
return errLockNotInitialized
|
||||
}
|
||||
|
||||
newLockInfo := debugLockInfo{
|
||||
lockOrigin: lockOrigin,
|
||||
status: "Blocked",
|
||||
@ -195,16 +186,15 @@ func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockOrigin, operationID s
|
||||
newLockInfo.lockType = debugWLockStr
|
||||
}
|
||||
|
||||
if lockInfo, ok := n.debugLockMap[param]; ok {
|
||||
if lockInfo == nil {
|
||||
// *debugLockInfoPerVolumePath entry is nil, initialize here to avoid any case of `nil` pointer access.
|
||||
n.initLockInfoForVolumePath(param)
|
||||
}
|
||||
} else {
|
||||
lockInfo, ok := n.debugLockMap[param]
|
||||
if !ok {
|
||||
// State info entry for the given <volume, pair> doesn't exist, initializing it.
|
||||
n.initLockInfoForVolumePath(param)
|
||||
}
|
||||
|
||||
if lockInfo == nil {
|
||||
// *debugLockInfoPerVolumePath entry is nil, initialize here to avoid any case of `nil` pointer access.
|
||||
n.initLockInfoForVolumePath(param)
|
||||
}
|
||||
// lockInfo is a map[string]debugLockInfo, which holds map[OperationID]{status,time, origin} of the lock.
|
||||
if n.debugLockMap[param].lockInfo == nil {
|
||||
n.debugLockMap[param].lockInfo = make(map[string]debugLockInfo)
|
||||
@ -224,45 +214,37 @@ func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockOrigin, operationID s
|
||||
|
||||
// deleteLockInfoEntry - Deletes the lock state information for given <volume, path> pair. Called when nsLk.ref count is 0.
|
||||
func (n *nsLockMap) deleteLockInfoEntryForVolumePath(param nsParam) error {
|
||||
if n.debugLockMap == nil {
|
||||
return errLockNotInitialized
|
||||
}
|
||||
// delete the lock info for the given operation.
|
||||
if _, found := n.debugLockMap[param]; found {
|
||||
// Remove from the map if there are no more references for the given (volume,path) pair.
|
||||
delete(n.debugLockMap, param)
|
||||
} else {
|
||||
if _, found := n.debugLockMap[param]; !found {
|
||||
return LockInfoVolPathMssing{param.volume, param.path}
|
||||
}
|
||||
// Remove from the map if there are no more references for the given (volume,path) pair.
|
||||
delete(n.debugLockMap, param)
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteLockInfoEntry - Deletes the entry for given opsID in the lock state information of given <volume, path> pair.
|
||||
// called when the nsLk ref count for the given <volume, path> pair is not 0.
|
||||
func (n *nsLockMap) deleteLockInfoEntryForOps(param nsParam, operationID string) error {
|
||||
if n.debugLockMap == nil {
|
||||
return errLockNotInitialized
|
||||
}
|
||||
// delete the lock info for the given operation.
|
||||
if infoMap, found := n.debugLockMap[param]; found {
|
||||
// the opertion finished holding the lock on the resource, remove the entry for the given operation with the operation ID.
|
||||
if _, foundInfo := infoMap.lockInfo[operationID]; foundInfo {
|
||||
// decrease the global running and lock reference counter.
|
||||
n.runningLockCounter--
|
||||
n.globalLockCounter--
|
||||
// decrease the lock referee counter for the lock info for given <volume,path> pair.
|
||||
// decrease the running operation number. Its assumed that the operation is over once an attempt to release the lock is made.
|
||||
infoMap.running--
|
||||
// decrease the total reference count of locks jeld on <volume,path> pair.
|
||||
infoMap.ref--
|
||||
delete(infoMap.lockInfo, operationID)
|
||||
} else {
|
||||
// Unlock request with invalid opertion ID not accepted.
|
||||
return LockInfoOpsIDNotFound{param.volume, param.path, operationID}
|
||||
}
|
||||
} else {
|
||||
infoMap, found := n.debugLockMap[param]
|
||||
if !found {
|
||||
return LockInfoVolPathMssing{param.volume, param.path}
|
||||
}
|
||||
// the opertion finished holding the lock on the resource, remove the entry for the given operation with the operation ID.
|
||||
if _, foundInfo := infoMap.lockInfo[operationID]; !foundInfo {
|
||||
// Unlock request with invalid opertion ID not accepted.
|
||||
return LockInfoOpsIDNotFound{param.volume, param.path, operationID}
|
||||
}
|
||||
// decrease the global running and lock reference counter.
|
||||
n.runningLockCounter--
|
||||
n.globalLockCounter--
|
||||
// decrease the lock referee counter for the lock info for given <volume,path> pair.
|
||||
// decrease the running operation number. Its assumed that the operation is over once an attempt to release the lock is made.
|
||||
infoMap.running--
|
||||
// decrease the total reference count of locks jeld on <volume,path> pair.
|
||||
infoMap.ref--
|
||||
delete(infoMap.lockInfo, operationID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -246,7 +246,6 @@ func TestNewDebugLockInfoPerVolumePath(t *testing.T) {
|
||||
|
||||
// TestNsLockMapStatusBlockedToRunning - Validates the function for changing the lock state from blocked to running.
|
||||
func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
volume string
|
||||
path string
|
||||
@ -327,9 +326,9 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
actualErr := nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin,
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedNilErr := errLockNotInitialized
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
expectedErr := LockInfoVolPathMssing{testCases[0].volume, testCases[0].path}
|
||||
if actualErr != expectedErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedErr, actualErr)
|
||||
}
|
||||
|
||||
nsMutex = &nsLockMap{
|
||||
@ -337,15 +336,13 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
debugLockMap: make(map[nsParam]*debugLockInfoPerVolumePath),
|
||||
lockMap: make(map[nsParam]*nsLock),
|
||||
}
|
||||
// Entry for <volume, path> pair is set to nil.
|
||||
// Should fail with `errLockNotInitialized`.
|
||||
// Entry for <volume, path> pair is set to nil. Should fail with `errLockNotInitialized`.
|
||||
nsMutex.debugLockMap[param] = nil
|
||||
actualErr = nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin,
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedNilErr = errLockNotInitialized
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
if actualErr != errLockNotInitialized {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", errLockNotInitialized, actualErr)
|
||||
}
|
||||
|
||||
// Setting the lock info the be `nil`.
|
||||
@ -391,10 +388,7 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
// set debug lock info to `nil` so that the next tests have to initialize them again.
|
||||
defer func() {
|
||||
nsMutex.debugLockMap = nil
|
||||
}()
|
||||
|
||||
// Iterate over the cases and assert the result.
|
||||
for i, testCase := range testCases {
|
||||
param := nsParam{testCase.volume, testCase.path}
|
||||
@ -518,22 +512,20 @@ func TestNsLockMapStatusNoneToBlocked(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
|
||||
param := nsParam{testCases[0].volume, testCases[0].path}
|
||||
// Testing before the initialization done.
|
||||
// Since the data structures for
|
||||
actualErr := nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin,
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedNilErr := errLockNotInitialized
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
expectedErr := LockInfoVolPathMssing{testCases[0].volume, testCases[0].path}
|
||||
if actualErr != expectedErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedErr, actualErr)
|
||||
}
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
// set debug lock info to `nil` so that the next tests have to initialize them again.
|
||||
defer func() {
|
||||
nsMutex.debugLockMap = nil
|
||||
}()
|
||||
|
||||
// Iterate over the cases and assert the result.
|
||||
for i, testCase := range testCases {
|
||||
nsMutex.lockMapMutex.Lock()
|
||||
@ -562,6 +554,10 @@ func TestNsLockMapDeleteLockInfoEntryForOps(t *testing.T) {
|
||||
// expected metrics.
|
||||
},
|
||||
}
|
||||
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
|
||||
// case - 1.
|
||||
// Testing the case where delete lock info is attempted even before the lock is initialized.
|
||||
param := nsParam{testCases[0].volume, testCases[0].path}
|
||||
@ -569,29 +565,12 @@ func TestNsLockMapDeleteLockInfoEntryForOps(t *testing.T) {
|
||||
|
||||
actualErr := nsMutex.deleteLockInfoEntryForOps(param, testCases[0].opsID)
|
||||
|
||||
expectedNilErr := errLockNotInitialized
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
expectedErr := LockInfoVolPathMssing{testCases[0].volume, testCases[0].path}
|
||||
if actualErr != expectedErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedErr, actualErr)
|
||||
}
|
||||
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
// set debug lock info to `nil` so that the next tests have to initialize them again.
|
||||
defer func() {
|
||||
nsMutex.debugLockMap = nil
|
||||
}()
|
||||
// case - 2.
|
||||
// Case where an attempt to delete the entry for non-existent <volume, path> pair is done.
|
||||
// Set the status of the lock to blocked and then to running.
|
||||
nonExistParam := nsParam{volume: "non-exist-volume", path: "non-exist-path"}
|
||||
actualErr = nsMutex.deleteLockInfoEntryForOps(nonExistParam, testCases[0].opsID)
|
||||
|
||||
expectedVolPathErr := LockInfoVolPathMssing{nonExistParam.volume, nonExistParam.path}
|
||||
if actualErr != expectedVolPathErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedVolPathErr, actualErr)
|
||||
}
|
||||
|
||||
// Case - 3.
|
||||
// Case - 2.
|
||||
// Lock state is set to Running and then an attempt to delete the info for non-existent opsID done.
|
||||
nsMutex.lockMapMutex.Lock()
|
||||
err := nsMutex.statusNoneToBlocked(param, testCases[0].lockOrigin, testCases[0].opsID, testCases[0].readLock)
|
||||
@ -660,36 +639,21 @@ func TestNsLockMapDeleteLockInfoEntryForVolumePath(t *testing.T) {
|
||||
// expected metrics.
|
||||
},
|
||||
}
|
||||
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
|
||||
// case - 1.
|
||||
// Testing the case where delete lock info is attempted even before the lock is initialized.
|
||||
// Case where an attempt to delete the entry for non-existent <volume, path> pair is done.
|
||||
// Set the status of the lock to blocked and then to running.
|
||||
param := nsParam{testCases[0].volume, testCases[0].path}
|
||||
// Testing before the initialization done.
|
||||
|
||||
actualErr := nsMutex.deleteLockInfoEntryForVolumePath(param)
|
||||
|
||||
expectedNilErr := errLockNotInitialized
|
||||
expectedNilErr := LockInfoVolPathMssing{param.volume, param.path}
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
}
|
||||
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
// set debug lock info to `nil` so that the next tests have to initialize them again.
|
||||
defer func() {
|
||||
nsMutex.debugLockMap = nil
|
||||
}()
|
||||
// case - 2.
|
||||
// Case where an attempt to delete the entry for non-existent <volume, path> pair is done.
|
||||
// Set the status of the lock to blocked and then to running.
|
||||
nonExistParam := nsParam{volume: "non-exist-volume", path: "non-exist-path"}
|
||||
actualErr = nsMutex.deleteLockInfoEntryForVolumePath(nonExistParam)
|
||||
|
||||
expectedVolPathErr := LockInfoVolPathMssing{nonExistParam.volume, nonExistParam.path}
|
||||
if actualErr != expectedVolPathErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedVolPathErr, actualErr)
|
||||
}
|
||||
|
||||
// case - 3.
|
||||
// Attempt to delete an registered entry is done.
|
||||
// All metrics should be 0 after deleting the entry.
|
||||
|
||||
|
@ -77,8 +77,8 @@ type lockServer struct {
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
// Initialize distributed name space lock.
|
||||
func initDistributedNSLock(mux *router.Router, serverConfig serverCmdConfig) {
|
||||
// Register distributed NS lock handlers.
|
||||
func registerDistNSLockRouter(mux *router.Router, serverConfig serverCmdConfig) {
|
||||
lockServers := newLockServers(serverConfig)
|
||||
registerStorageLockers(mux, lockServers)
|
||||
}
|
||||
|
@ -72,10 +72,6 @@ func initNSLock(isDist bool) {
|
||||
nsMutex.debugLockMap = make(map[nsParam]*debugLockInfoPerVolumePath)
|
||||
}
|
||||
|
||||
func (n *nsLockMap) initLockInfoForVolumePath(param nsParam) {
|
||||
n.debugLockMap[param] = newDebugLockInfoPerVolumePath()
|
||||
}
|
||||
|
||||
// RWLocker - interface that any read-write locking library should implement.
|
||||
type RWLocker interface {
|
||||
sync.Locker
|
||||
|
@ -301,11 +301,6 @@ func TestLockStats(t *testing.T) {
|
||||
// initializing the locks.
|
||||
initNSLock(false)
|
||||
|
||||
// set debug lock info to `nil` so that the next tests have to initialize them again.
|
||||
defer func() {
|
||||
nsMutex.debugLockMap = nil
|
||||
}()
|
||||
|
||||
// hold 10 read locks.
|
||||
for i := 0; i < 10; i++ {
|
||||
nsMutex.RLock("my-bucket", "my-object", strconv.Itoa(i))
|
||||
|
@ -17,13 +17,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/minio/minio/pkg/disk"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio/pkg/disk"
|
||||
)
|
||||
|
||||
// naughtyDisk wraps a POSIX disk and returns programmed errors
|
||||
// specified by the developer. The purpose is to simulate errors
|
||||
// that are hard to simulate in practise like DiskNotFound.
|
||||
// that are hard to simulate in practice like DiskNotFound.
|
||||
// Programmed errors are stored in errors field.
|
||||
type naughtyDisk struct {
|
||||
// The real disk
|
||||
@ -42,6 +43,10 @@ func newNaughtyDisk(d *posix, errs map[int]error, defaultErr error) *naughtyDisk
|
||||
return &naughtyDisk{disk: d, errors: errs, defaultErr: defaultErr}
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) String() string {
|
||||
return d.String()
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) calcError() (err error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
@ -65,11 +65,12 @@ func (rpcClient *RPCClient) getRPCClient() *rpc.Client {
|
||||
// dialRPCClient tries to establish a connection to the server in a safe manner
|
||||
func (rpcClient *RPCClient) dialRPCClient() (*rpc.Client, error) {
|
||||
rpcClient.mu.Lock()
|
||||
defer rpcClient.mu.Unlock()
|
||||
// After acquiring lock, check whether another thread may not have already dialed and established connection
|
||||
if rpcClient.rpcPrivate != nil {
|
||||
rpcClient.mu.Unlock()
|
||||
return rpcClient.rpcPrivate, nil
|
||||
}
|
||||
rpcClient.mu.Unlock()
|
||||
|
||||
var err error
|
||||
var conn net.Conn
|
||||
@ -92,7 +93,9 @@ func (rpcClient *RPCClient) dialRPCClient() (*rpc.Client, error) {
|
||||
if rpc == nil {
|
||||
return nil, errors.New("No valid RPC Client created after dial")
|
||||
}
|
||||
rpcClient.mu.Lock()
|
||||
rpcClient.rpcPrivate = rpc
|
||||
rpcClient.mu.Unlock()
|
||||
return rpc, nil
|
||||
}
|
||||
if err == nil {
|
||||
|
@ -564,6 +564,18 @@ func testListObjects(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
||||
}
|
||||
}
|
||||
|
||||
func initFSObjectsB(disk string, t *testing.B) (obj ObjectLayer) {
|
||||
storageDisks, err := initStorageDisks([]string{disk}, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
obj, err = newFSObjects(storageDisks[0])
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
func BenchmarkListObjects(b *testing.B) {
|
||||
// Make a temporary directory to use as the obj.
|
||||
directory, err := ioutil.TempDir("", "minio-list-benchmark")
|
||||
@ -573,10 +585,7 @@ func BenchmarkListObjects(b *testing.B) {
|
||||
defer removeAll(directory)
|
||||
|
||||
// Create the obj.
|
||||
obj, err := newFSObjects(directory)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
obj := initFSObjectsB(directory, b)
|
||||
|
||||
// Create a bucket.
|
||||
err = obj.MakeBucket("ls-benchmark-bucket")
|
||||
|
@ -105,9 +105,6 @@ func isLocalStorage(networkPath string) bool {
|
||||
// Depending on the disk type network or local, initialize storage API.
|
||||
func newStorageAPI(disk string) (storage StorageAPI, err error) {
|
||||
if isLocalStorage(disk) {
|
||||
if idx := strings.LastIndex(disk, ":"); idx != -1 {
|
||||
return newPosix(disk[idx+1:])
|
||||
}
|
||||
return newPosix(disk)
|
||||
}
|
||||
return newRPCClient(disk)
|
||||
|
@ -18,12 +18,35 @@ package cmd
|
||||
|
||||
import "time"
|
||||
|
||||
// BackendType - represents different backend types.
|
||||
type BackendType int
|
||||
|
||||
// Enum for different backend types.
|
||||
const (
|
||||
Unknown BackendType = iota
|
||||
// Filesystem backend.
|
||||
FS
|
||||
// Multi disk single node XL backend.
|
||||
XL
|
||||
// Add your own backend.
|
||||
)
|
||||
|
||||
// StorageInfo - represents total capacity of underlying storage.
|
||||
type StorageInfo struct {
|
||||
// Total disk space.
|
||||
Total int64
|
||||
// Free available disk space.
|
||||
Free int64
|
||||
// Backend type.
|
||||
Backend struct {
|
||||
// Represents various backend types, currently on FS and XL.
|
||||
Type BackendType
|
||||
|
||||
// Following fields are only meaningful if BackendType is XL.
|
||||
OnlineDisks int // Online disks during server startup.
|
||||
OfflineDisks int // Offline disks during server startup.
|
||||
Quorum int // Minimum disks required for successful operations.
|
||||
}
|
||||
}
|
||||
|
||||
// BucketInfo - represents bucket metadata.
|
||||
|
@ -22,7 +22,6 @@ import "io"
|
||||
type ObjectLayer interface {
|
||||
// Storage operations.
|
||||
Shutdown() error
|
||||
HealDiskMetadata() error
|
||||
StorageInfo() StorageInfo
|
||||
|
||||
// Bucket operations.
|
||||
|
25
cmd/posix.go
25
cmd/posix.go
@ -40,10 +40,11 @@ const (
|
||||
|
||||
// posix - implements StorageAPI interface.
|
||||
type posix struct {
|
||||
ioErrCount int32 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||
diskPath string
|
||||
minFreeSpace int64
|
||||
minFreeInodes int64
|
||||
ioErrCount int32 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||
diskPath string
|
||||
suppliedDiskPath string
|
||||
minFreeSpace int64
|
||||
minFreeInodes int64
|
||||
}
|
||||
|
||||
var errFaultyDisk = errors.New("Faulty disk")
|
||||
@ -95,6 +96,10 @@ func newPosix(diskPath string) (StorageAPI, error) {
|
||||
if diskPath == "" {
|
||||
return nil, errInvalidArgument
|
||||
}
|
||||
suppliedDiskPath := diskPath
|
||||
if idx := strings.LastIndex(diskPath, ":"); idx != -1 {
|
||||
diskPath = diskPath[idx+1:]
|
||||
}
|
||||
var err error
|
||||
// Disallow relative paths, figure out absolute paths.
|
||||
diskPath, err = filepath.Abs(diskPath)
|
||||
@ -102,9 +107,10 @@ func newPosix(diskPath string) (StorageAPI, error) {
|
||||
return nil, err
|
||||
}
|
||||
fs := &posix{
|
||||
diskPath: diskPath,
|
||||
minFreeSpace: fsMinFreeSpace,
|
||||
minFreeInodes: fsMinFreeInodesPercent,
|
||||
suppliedDiskPath: suppliedDiskPath,
|
||||
diskPath: diskPath,
|
||||
minFreeSpace: fsMinFreeSpace,
|
||||
minFreeInodes: fsMinFreeInodesPercent,
|
||||
}
|
||||
st, err := os.Stat(preparePath(diskPath))
|
||||
if err != nil {
|
||||
@ -162,6 +168,11 @@ func (s posix) checkDiskFree() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements stringer compatible interface.
|
||||
func (s *posix) String() string {
|
||||
return s.suppliedDiskPath
|
||||
}
|
||||
|
||||
// DiskInfo provides current information about disk space usage,
|
||||
// total free inodes and underlying filesystem.
|
||||
func (s *posix) DiskInfo() (info disk.Info, err error) {
|
||||
|
@ -66,6 +66,18 @@ func TestPostPolicyHandler(t *testing.T) {
|
||||
|
||||
// testPostPolicyHandler - Tests validate post policy handler uploading objects.
|
||||
func testPostPolicyHandler(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
||||
root, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Initializing config.json failed")
|
||||
}
|
||||
defer removeAll(root)
|
||||
|
||||
// Register event notifier.
|
||||
err = initEventNotifier(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("Initializing event notifiers failed")
|
||||
}
|
||||
|
||||
// get random bucket name.
|
||||
bucketName := getRandomBucketName()
|
||||
|
||||
|
136
cmd/prepare-storage-msg.go
Normal file
136
cmd/prepare-storage-msg.go
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/mc/pkg/console"
|
||||
)
|
||||
|
||||
// Helper to generate integer sequences into a friendlier user consumable format.
|
||||
func int2Str(i int, t int) string {
|
||||
if i < 10 {
|
||||
if t < 10 {
|
||||
return fmt.Sprintf("0%d/0%d", i, t)
|
||||
}
|
||||
return fmt.Sprintf("0%d/%d", i, t)
|
||||
}
|
||||
return fmt.Sprintf("%d/%d", i, t)
|
||||
}
|
||||
|
||||
// Print a given message once.
|
||||
type printOnceFunc func(msg string)
|
||||
|
||||
// Print once is a constructor returning a function printing once.
|
||||
// internally print uses sync.Once to perform exactly one action.
|
||||
func printOnceFn() printOnceFunc {
|
||||
var once sync.Once
|
||||
return func(msg string) {
|
||||
once.Do(func() { console.Println(msg) })
|
||||
}
|
||||
}
|
||||
|
||||
// Prints custom message when healing is required for XL and Distributed XL backend.
|
||||
func printHealMsg(firstEndpoint string, storageDisks []StorageAPI, fn printOnceFunc) {
|
||||
msg := getHealMsg(firstEndpoint, storageDisks)
|
||||
fn(msg)
|
||||
}
|
||||
|
||||
// Constructs a formatted heal message, when cluster is found to be in state where it requires healing.
|
||||
// healing is optional, server continues to initialize object layer after printing this message.
|
||||
// it is upto the end user to perform a heal if needed.
|
||||
func getHealMsg(firstEndpoint string, storageDisks []StorageAPI) string {
|
||||
msg := fmt.Sprintln("\nData volume requires HEALING. Please run the following command:")
|
||||
msg += "MINIO_ACCESS_KEY=%s "
|
||||
msg += "MINIO_SECRET_KEY=%s "
|
||||
msg += "minio control heal %s"
|
||||
creds := serverConfig.GetCredential()
|
||||
msg = fmt.Sprintf(msg, creds.AccessKeyID, creds.SecretAccessKey, firstEndpoint)
|
||||
disksInfo, _, _ := getDisksInfo(storageDisks)
|
||||
for i, info := range disksInfo {
|
||||
msg += fmt.Sprintf(
|
||||
"\n[%s] %s - %s %s",
|
||||
int2Str(i+1, len(storageDisks)),
|
||||
storageDisks[i],
|
||||
humanize.IBytes(uint64(info.Total)),
|
||||
func() string {
|
||||
if info.Total > 0 {
|
||||
return "online"
|
||||
}
|
||||
return "offline"
|
||||
}(),
|
||||
)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Prints regular message when we have sufficient disks to start the cluster.
|
||||
func printRegularMsg(storageDisks []StorageAPI, fn printOnceFunc) {
|
||||
msg := getRegularMsg(storageDisks)
|
||||
fn(msg)
|
||||
}
|
||||
|
||||
// Constructs a formatted regular message when we have sufficient disks to start the cluster.
|
||||
func getRegularMsg(storageDisks []StorageAPI) string {
|
||||
msg := colorBlue("\nInitializing data volume.")
|
||||
disksInfo, _, _ := getDisksInfo(storageDisks)
|
||||
for i, info := range disksInfo {
|
||||
msg += fmt.Sprintf(
|
||||
"\n[%s] %s - %s %s",
|
||||
int2Str(i+1, len(storageDisks)),
|
||||
storageDisks[i],
|
||||
humanize.IBytes(uint64(info.Total)),
|
||||
func() string {
|
||||
if info.Total > 0 {
|
||||
return "online"
|
||||
}
|
||||
return "offline"
|
||||
}(),
|
||||
)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Prints initialization message when cluster is being initialized for the first time.
|
||||
func printFormatMsg(storageDisks []StorageAPI, fn printOnceFunc) {
|
||||
msg := getFormatMsg(storageDisks)
|
||||
fn(msg)
|
||||
}
|
||||
|
||||
// Generate a formatted message when cluster is being initialized for the first time.
|
||||
func getFormatMsg(storageDisks []StorageAPI) string {
|
||||
msg := colorBlue("\nInitializing data volume for the first time.")
|
||||
disksInfo, _, _ := getDisksInfo(storageDisks)
|
||||
for i, info := range disksInfo {
|
||||
msg += fmt.Sprintf(
|
||||
"\n[%s] %s - %s %s",
|
||||
int2Str(i+1, len(storageDisks)),
|
||||
storageDisks[i],
|
||||
humanize.IBytes(uint64(info.Total)),
|
||||
func() string {
|
||||
if info.Total > 0 {
|
||||
return "online"
|
||||
}
|
||||
return "offline"
|
||||
}(),
|
||||
)
|
||||
}
|
||||
return msg
|
||||
}
|
88
cmd/prepare-storage-msg_test.go
Normal file
88
cmd/prepare-storage-msg_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import "testing"
|
||||
|
||||
// Tests heal message to be correct and properly formatted.
|
||||
func TestHealMsg(t *testing.T) {
|
||||
storageDisks, fsDirs := prepareXLStorageDisks(t)
|
||||
defer removeRoots(fsDirs)
|
||||
testCases := []struct {
|
||||
endPoint string
|
||||
storageDisks []StorageAPI
|
||||
}{
|
||||
{
|
||||
endPoint: "http://10.1.10.1:9000",
|
||||
storageDisks: storageDisks,
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
msg := getHealMsg(testCase.endPoint, testCase.storageDisks)
|
||||
if msg == "" {
|
||||
t.Fatalf("Test: %d Unable to get heal message.", i+1)
|
||||
}
|
||||
msg = getRegularMsg(testCase.storageDisks)
|
||||
if msg == "" {
|
||||
t.Fatalf("Test: %d Unable to get regular message.", i+1)
|
||||
}
|
||||
msg = getFormatMsg(testCase.storageDisks)
|
||||
if msg == "" {
|
||||
t.Fatalf("Test: %d Unable to get format message.", i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests disk info, validates if we do return proper disk info structure
|
||||
// even in case of certain disks not available.
|
||||
func TestDisksInfo(t *testing.T) {
|
||||
storageDisks, fsDirs := prepareXLStorageDisks(t)
|
||||
defer removeRoots(fsDirs)
|
||||
|
||||
testCases := []struct {
|
||||
storageDisks []StorageAPI
|
||||
onlineDisks int
|
||||
offlineDisks int
|
||||
}{
|
||||
{
|
||||
storageDisks: storageDisks,
|
||||
onlineDisks: 16,
|
||||
offlineDisks: 0,
|
||||
},
|
||||
{
|
||||
storageDisks: prepareNOfflineDisks(deepCopyStorageDisks(storageDisks), 4, t),
|
||||
onlineDisks: 12,
|
||||
offlineDisks: 4,
|
||||
},
|
||||
{
|
||||
storageDisks: prepareNOfflineDisks(deepCopyStorageDisks(storageDisks), 16, t),
|
||||
onlineDisks: 0,
|
||||
offlineDisks: 16,
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
_, onlineDisks, offlineDisks := getDisksInfo(testCase.storageDisks)
|
||||
if testCase.onlineDisks != onlineDisks {
|
||||
t.Errorf("Test %d: Expected online disks %d, got %d", i+1, testCase.onlineDisks, onlineDisks)
|
||||
}
|
||||
if testCase.offlineDisks != offlineDisks {
|
||||
t.Errorf("Test %d: Expected offline disks %d, got %d", i+1, testCase.offlineDisks, offlineDisks)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -19,6 +19,7 @@ package cmd
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/mc/pkg/console"
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
)
|
||||
|
||||
@ -49,24 +50,23 @@ func init() {
|
||||
| Quorum | Quorum Formatted | |
|
||||
+----------+--------------------------+-----------------------+
|
||||
| All | Quorum | Print message saying |
|
||||
| | Formatted, | "Heal via minioctl" |
|
||||
| | Formatted, | "Heal via control" |
|
||||
| | some unformatted | and initObjectLayer |
|
||||
+----------+--------------------------+-----------------------+
|
||||
| All | None Formatted | FormatDisks |
|
||||
| | | and initObjectLayer |
|
||||
| | | |
|
||||
+----------+--------------------------+-----------------------+
|
||||
| | | Wait for notify from |
|
||||
| Quorum | | "Heal via minioctl" |
|
||||
| | Quorum UnFormatted | |
|
||||
+----------+--------------------------+-----------------------+
|
||||
| No | | Wait till enough |
|
||||
| Quorum | _ | nodes are online and |
|
||||
| | | one of the above |
|
||||
| | | sections apply |
|
||||
+----------+--------------------------+-----------------------+
|
||||
| | | |
|
||||
| Quorum | Quorum UnFormatted | Abort |
|
||||
+----------+--------------------------+-----------------------+
|
||||
|
||||
N B A disk can be in one of the following states.
|
||||
A disk can be in one of the following states.
|
||||
- Unformatted
|
||||
- Formatted
|
||||
- Corrupted
|
||||
@ -101,7 +101,7 @@ const (
|
||||
Abort
|
||||
)
|
||||
|
||||
func prepForInit(disks []string, sErrs []error, diskCount int) InitActions {
|
||||
func prepForInitXL(firstDisk bool, sErrs []error, diskCount int) InitActions {
|
||||
// Count errors by error value.
|
||||
errMap := make(map[error]int)
|
||||
for _, err := range sErrs {
|
||||
@ -114,34 +114,7 @@ func prepForInit(disks []string, sErrs []error, diskCount int) InitActions {
|
||||
disksUnformatted := errMap[errUnformattedDisk]
|
||||
disksCorrupted := errMap[errCorruptedFormat]
|
||||
|
||||
// All disks are unformatted, proceed to formatting disks.
|
||||
if disksUnformatted == diskCount {
|
||||
// Only the first server formats an uninitialized setup, others wait for notification.
|
||||
if isLocalStorage(disks[0]) {
|
||||
return FormatDisks
|
||||
}
|
||||
return WaitForFormatting
|
||||
} else if disksUnformatted >= quorum {
|
||||
if disksUnformatted+disksOffline == diskCount {
|
||||
return WaitForAll
|
||||
}
|
||||
// Some disks possibly corrupted.
|
||||
return WaitForHeal
|
||||
}
|
||||
|
||||
// Already formatted, proceed to initialization of object layer.
|
||||
if disksFormatted == diskCount {
|
||||
return InitObjectLayer
|
||||
} else if disksFormatted >= quorum {
|
||||
if (disksFormatted+disksOffline == diskCount) ||
|
||||
(disksFormatted+disksUnformatted == diskCount) {
|
||||
return InitObjectLayer
|
||||
}
|
||||
// Some disks possibly corrupted.
|
||||
return WaitForHeal
|
||||
}
|
||||
|
||||
// No Quorum.
|
||||
// No Quorum lots of offline disks, wait for quorum.
|
||||
if disksOffline >= quorum {
|
||||
return WaitForQuorum
|
||||
}
|
||||
@ -151,57 +124,113 @@ func prepForInit(disks []string, sErrs []error, diskCount int) InitActions {
|
||||
if disksCorrupted >= quorum {
|
||||
return Abort
|
||||
}
|
||||
// Some of the formatted disks are possibly offline.
|
||||
return WaitForHeal
|
||||
}
|
||||
|
||||
func retryFormattingDisks(disks []string, storageDisks []StorageAPI) ([]StorageAPI, error) {
|
||||
nextBackoff := time.Duration(0)
|
||||
var err error
|
||||
done := false
|
||||
for !done {
|
||||
select {
|
||||
case <-time.After(nextBackoff * time.Second):
|
||||
// Attempt to load all `format.json`.
|
||||
_, sErrs := loadAllFormats(storageDisks)
|
||||
switch prepForInit(disks, sErrs, len(storageDisks)) {
|
||||
case Abort:
|
||||
err = errCorruptedFormat
|
||||
done = true
|
||||
case FormatDisks:
|
||||
err = initFormatXL(storageDisks)
|
||||
done = true
|
||||
case InitObjectLayer:
|
||||
err = nil
|
||||
done = true
|
||||
}
|
||||
case <-globalWakeupCh:
|
||||
// Reset nextBackoff to reduce the subsequent wait and re-read
|
||||
// format.json from all disks again.
|
||||
nextBackoff = 0
|
||||
// All disks are unformatted, proceed to formatting disks.
|
||||
if disksUnformatted == diskCount {
|
||||
// Only the first server formats an uninitialized setup, others wait for notification.
|
||||
if firstDisk { // First node always initializes.
|
||||
return FormatDisks
|
||||
}
|
||||
return WaitForFormatting
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
// Total disks unformatted are in quorum verify if we have some offline disks.
|
||||
if disksUnformatted >= quorum {
|
||||
// Some disks offline and some disks unformatted, wait for all of them to come online.
|
||||
if disksUnformatted+disksOffline == diskCount {
|
||||
return WaitForAll
|
||||
}
|
||||
// Some disks possibly corrupted and too many unformatted disks.
|
||||
return Abort
|
||||
}
|
||||
return storageDisks, nil
|
||||
|
||||
// Already formatted and in quorum, proceed to initialization of object layer.
|
||||
if disksFormatted >= quorum {
|
||||
if disksFormatted+disksOffline == diskCount {
|
||||
return InitObjectLayer
|
||||
}
|
||||
// Some of the formatted disks are possibly corrupted or unformatted, heal them.
|
||||
return WaitForHeal
|
||||
} // No quorum wait for quorum number of disks.
|
||||
return WaitForQuorum
|
||||
}
|
||||
|
||||
func waitForFormattingDisks(disks, ignoredDisks []string) ([]StorageAPI, error) {
|
||||
// FS Setup
|
||||
// Implements a jitter backoff loop for formatting all disks during
|
||||
// initialization of the server.
|
||||
func retryFormattingDisks(firstDisk bool, firstEndpoint string, storageDisks []StorageAPI) error {
|
||||
if storageDisks == nil {
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
// Create a done channel to control 'ListObjects' go routine.
|
||||
doneCh := make(chan struct{}, 1)
|
||||
|
||||
// Indicate to our routine to exit cleanly upon return.
|
||||
defer close(doneCh)
|
||||
|
||||
// Wait on the jitter retry loop.
|
||||
for range newRetryTimer(time.Second, time.Second*30, MaxJitter, doneCh) {
|
||||
// Attempt to load all `format.json`.
|
||||
formatConfigs, sErrs := loadAllFormats(storageDisks)
|
||||
// Check if this is a XL or distributed XL, anything > 1 is considered XL backend.
|
||||
if len(formatConfigs) > 1 {
|
||||
switch prepForInitXL(firstDisk, sErrs, len(storageDisks)) {
|
||||
case Abort:
|
||||
return errCorruptedFormat
|
||||
case FormatDisks:
|
||||
console.Eraseline()
|
||||
printFormatMsg(storageDisks, printOnceFn())
|
||||
return initFormatXL(storageDisks)
|
||||
case InitObjectLayer:
|
||||
console.Eraseline()
|
||||
// Validate formats load before proceeding forward.
|
||||
err := genericFormatCheck(formatConfigs, sErrs)
|
||||
if err == nil {
|
||||
printRegularMsg(storageDisks, printOnceFn())
|
||||
}
|
||||
return err
|
||||
case WaitForHeal:
|
||||
// Validate formats load before proceeding forward.
|
||||
err := genericFormatCheck(formatConfigs, sErrs)
|
||||
if err == nil {
|
||||
printHealMsg(firstEndpoint, storageDisks, printOnceFn())
|
||||
}
|
||||
return err
|
||||
case WaitForQuorum:
|
||||
console.Printf(
|
||||
"Initializing data volume. Waiting for minimum %d servers to come online.\n",
|
||||
len(storageDisks)/2+1,
|
||||
)
|
||||
case WaitForAll:
|
||||
console.Println("Initializing data volume for first time. Waiting for other servers to come online.")
|
||||
case WaitForFormatting:
|
||||
console.Println("Initializing data volume for first time. Waiting for first server to come online.")
|
||||
}
|
||||
continue
|
||||
} // else We have FS backend now. Check fs format as well now.
|
||||
if isFormatFound(formatConfigs) {
|
||||
console.Eraseline()
|
||||
// Validate formats load before proceeding forward.
|
||||
return genericFormatCheck(formatConfigs, sErrs)
|
||||
} // else initialize the format for FS.
|
||||
return initFormatFS(storageDisks[0])
|
||||
} // Return here.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize storage disks based on input arguments.
|
||||
func initStorageDisks(disks, ignoredDisks []string) ([]StorageAPI, error) {
|
||||
// Single disk means we will use FS backend.
|
||||
if len(disks) == 1 {
|
||||
storage, err := newStorageAPI(disks[0])
|
||||
if err != nil && err != errDiskNotFound {
|
||||
return nil, err
|
||||
}
|
||||
return []StorageAPI{storage}, nil
|
||||
}
|
||||
|
||||
// XL Setup
|
||||
} // Otherwise proceed with XL setup.
|
||||
if err := checkSufficientDisks(disks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
disksSet := set.NewStringSet()
|
||||
if len(ignoredDisks) > 0 {
|
||||
disksSet = set.CreateStringSet(ignoredDisks...)
|
||||
@ -223,6 +252,30 @@ func waitForFormattingDisks(disks, ignoredDisks []string) ([]StorageAPI, error)
|
||||
}
|
||||
storageDisks[index] = storage
|
||||
}
|
||||
// Start wait loop retrying formatting disks.
|
||||
return retryFormattingDisks(disks, storageDisks)
|
||||
return storageDisks, nil
|
||||
}
|
||||
|
||||
// Format disks before initialization object layer.
|
||||
func waitForFormatDisks(firstDisk bool, firstEndpoint string, storageDisks []StorageAPI) (err error) {
|
||||
if storageDisks == nil {
|
||||
return errInvalidArgument
|
||||
}
|
||||
// Start retry loop retrying until disks are formatted properly, until we have reached
|
||||
// a conditional quorum of formatted disks.
|
||||
err = retryFormattingDisks(firstDisk, firstEndpoint, storageDisks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if firstDisk {
|
||||
// Notify every one else that they can try init again.
|
||||
for _, storage := range storageDisks {
|
||||
switch store := storage.(type) {
|
||||
// Wake up remote storage servers to initiate init again.
|
||||
case networkStorage:
|
||||
var reply GenericReply
|
||||
_ = store.rpcClient.Call("Storage.TryInitHandler", &GenericArgs{}, &reply)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -16,10 +16,7 @@
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
import "testing"
|
||||
|
||||
func (action InitActions) String() string {
|
||||
switch action {
|
||||
@ -41,43 +38,8 @@ func (action InitActions) String() string {
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
func TestPrepForInit(t *testing.T) {
|
||||
var disks []string
|
||||
if runtime.GOOS == "windows" {
|
||||
disks = []string{
|
||||
`c:\mnt\disk1`,
|
||||
`c:\mnt\disk2`,
|
||||
`c:\mnt\disk3`,
|
||||
`c:\mnt\disk4`,
|
||||
`c:\mnt\disk5`,
|
||||
`c:\mnt\disk6`,
|
||||
`c:\mnt\disk7`,
|
||||
`c:\mnt\disk8`,
|
||||
}
|
||||
} else {
|
||||
disks = []string{
|
||||
"/mnt/disk1",
|
||||
"/mnt/disk2",
|
||||
"/mnt/disk3",
|
||||
"/mnt/disk4",
|
||||
"/mnt/disk5",
|
||||
"/mnt/disk6",
|
||||
"/mnt/disk7",
|
||||
"/mnt/disk8",
|
||||
}
|
||||
}
|
||||
// Building up disks that resolve to localhost and remote w.r.t isLocalStorage().
|
||||
var (
|
||||
disksLocal []string
|
||||
disksRemote []string
|
||||
)
|
||||
for i := range disks {
|
||||
disksLocal = append(disksLocal, "localhost:"+disks[i])
|
||||
}
|
||||
// Using 4.4.4.4 as a known non-local address.
|
||||
for i := range disks {
|
||||
disksRemote = append(disksRemote, "4.4.4.4:"+disks[i])
|
||||
}
|
||||
|
||||
func TestPrepForInitXL(t *testing.T) {
|
||||
// All disks are unformatted, a fresh setup.
|
||||
allUnformatted := []error{
|
||||
errUnformattedDisk, errUnformattedDisk, errUnformattedDisk, errUnformattedDisk,
|
||||
@ -120,32 +82,32 @@ func TestPrepForInit(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
// Params for prepForInit().
|
||||
disks []string
|
||||
firstDisk bool
|
||||
errs []error
|
||||
diskCount int
|
||||
action InitActions
|
||||
}{
|
||||
// Local disks.
|
||||
{disksLocal, allFormatted, 8, InitObjectLayer},
|
||||
{disksLocal, quorumFormatted, 8, InitObjectLayer},
|
||||
{disksLocal, allUnformatted, 8, FormatDisks},
|
||||
{disksLocal, quorumUnformatted, 8, WaitForAll},
|
||||
{disksLocal, quorumUnformattedSomeCorrupted, 8, WaitForHeal},
|
||||
{disksLocal, noQuourm, 8, WaitForQuorum},
|
||||
{disksLocal, minorityCorrupted, 8, WaitForHeal},
|
||||
{disksLocal, majorityCorrupted, 8, Abort},
|
||||
{true, allFormatted, 8, InitObjectLayer},
|
||||
{true, quorumFormatted, 8, InitObjectLayer},
|
||||
{true, allUnformatted, 8, FormatDisks},
|
||||
{true, quorumUnformatted, 8, WaitForAll},
|
||||
{true, quorumUnformattedSomeCorrupted, 8, Abort},
|
||||
{true, noQuourm, 8, WaitForQuorum},
|
||||
{true, minorityCorrupted, 8, WaitForHeal},
|
||||
{true, majorityCorrupted, 8, Abort},
|
||||
// Remote disks.
|
||||
{disksRemote, allFormatted, 8, InitObjectLayer},
|
||||
{disksRemote, quorumFormatted, 8, InitObjectLayer},
|
||||
{disksRemote, allUnformatted, 8, WaitForFormatting},
|
||||
{disksRemote, quorumUnformatted, 8, WaitForAll},
|
||||
{disksRemote, quorumUnformattedSomeCorrupted, 8, WaitForHeal},
|
||||
{disksRemote, noQuourm, 8, WaitForQuorum},
|
||||
{disksRemote, minorityCorrupted, 8, WaitForHeal},
|
||||
{disksRemote, majorityCorrupted, 8, Abort},
|
||||
{false, allFormatted, 8, InitObjectLayer},
|
||||
{false, quorumFormatted, 8, InitObjectLayer},
|
||||
{false, allUnformatted, 8, WaitForFormatting},
|
||||
{false, quorumUnformatted, 8, WaitForAll},
|
||||
{false, quorumUnformattedSomeCorrupted, 8, Abort},
|
||||
{false, noQuourm, 8, WaitForQuorum},
|
||||
{false, minorityCorrupted, 8, WaitForHeal},
|
||||
{false, majorityCorrupted, 8, Abort},
|
||||
}
|
||||
for i, test := range testCases {
|
||||
actual := prepForInit(test.disks, test.errs, test.diskCount)
|
||||
actual := prepForInitXL(test.firstDisk, test.errs, test.diskCount)
|
||||
if actual != test.action {
|
||||
t.Errorf("Test %d expected %s but receieved %s\n", i+1, test.action, actual)
|
||||
}
|
||||
|
109
cmd/retry.go
Normal file
109
cmd/retry.go
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// lockedRandSource provides protected rand source, implements rand.Source interface.
|
||||
type lockedRandSource struct {
|
||||
lk sync.Mutex
|
||||
src rand.Source
|
||||
}
|
||||
|
||||
// Int63 returns a non-negative pseudo-random 63-bit integer as an
|
||||
// int64.
|
||||
func (r *lockedRandSource) Int63() (n int64) {
|
||||
r.lk.Lock()
|
||||
n = r.src.Int63()
|
||||
r.lk.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Seed uses the provided seed value to initialize the generator to a
|
||||
// deterministic state.
|
||||
func (r *lockedRandSource) Seed(seed int64) {
|
||||
r.lk.Lock()
|
||||
r.src.Seed(seed)
|
||||
r.lk.Unlock()
|
||||
}
|
||||
|
||||
// MaxRetry is the maximum number of retries before stopping.
|
||||
var MaxRetry = 5
|
||||
|
||||
// MaxJitter will randomize over the full exponential backoff time
|
||||
const MaxJitter = 1.0
|
||||
|
||||
// NoJitter disables the use of jitter for randomizing the exponential backoff time
|
||||
const NoJitter = 0.0
|
||||
|
||||
// Global random source for fetching random values.
|
||||
var globalRandomSource = rand.New(&lockedRandSource{
|
||||
src: rand.NewSource(time.Now().UTC().UnixNano()),
|
||||
})
|
||||
|
||||
// newRetryTimer creates a timer with exponentially increasing delays
|
||||
// until the maximum retry attempts are reached.
|
||||
func newRetryTimer(unit time.Duration, cap time.Duration, jitter float64, doneCh chan struct{}) <-chan struct{} {
|
||||
attemptCh := make(chan struct{})
|
||||
|
||||
// computes the exponential backoff duration according to
|
||||
// https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||
exponentialBackoffWait := func(attempt int) time.Duration {
|
||||
// normalize jitter to the range [0, 1.0]
|
||||
if jitter < NoJitter {
|
||||
jitter = NoJitter
|
||||
}
|
||||
if jitter > MaxJitter {
|
||||
jitter = MaxJitter
|
||||
}
|
||||
|
||||
//sleep = random_between(0, min(cap, base * 2 ** attempt))
|
||||
sleep := unit * time.Duration(1<<uint(attempt))
|
||||
if sleep > cap {
|
||||
sleep = cap
|
||||
}
|
||||
if jitter != NoJitter {
|
||||
sleep -= time.Duration(globalRandomSource.Float64() * float64(sleep) * jitter)
|
||||
}
|
||||
return sleep
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(attemptCh)
|
||||
var nextBackoff int
|
||||
for {
|
||||
select {
|
||||
// Attempts starts.
|
||||
case attemptCh <- struct{}{}:
|
||||
nextBackoff++
|
||||
case <-globalWakeupCh:
|
||||
// Reset nextBackoff to reduce the subsequent wait and re-read
|
||||
// format.json from all disks again.
|
||||
nextBackoff = 0
|
||||
case <-doneCh:
|
||||
// Stop the routine.
|
||||
return
|
||||
}
|
||||
time.Sleep(exponentialBackoffWait(nextBackoff))
|
||||
}
|
||||
}()
|
||||
return attemptCh
|
||||
}
|
@ -31,15 +31,15 @@ func newObjectLayerFn() ObjectLayer {
|
||||
}
|
||||
|
||||
// newObjectLayer - initialize any object layer depending on the number of disks.
|
||||
func newObjectLayer(disks, ignoredDisks []string) (ObjectLayer, error) {
|
||||
func newObjectLayer(storageDisks []StorageAPI) (ObjectLayer, error) {
|
||||
var objAPI ObjectLayer
|
||||
var err error
|
||||
if len(disks) == 1 {
|
||||
if len(storageDisks) == 1 {
|
||||
// Initialize FS object layer.
|
||||
objAPI, err = newFSObjects(disks[0])
|
||||
objAPI, err = newFSObjects(storageDisks[0])
|
||||
} else {
|
||||
// Initialize XL object layer.
|
||||
objAPI, err = newXLObjects(disks, ignoredDisks)
|
||||
objAPI, err = newXLObjects(storageDisks)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -58,20 +58,18 @@ func newObjectLayer(disks, ignoredDisks []string) (ObjectLayer, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Register the callback that should be called when the process shuts down.
|
||||
globalShutdownCBs.AddObjectLayerCB(func() errCode {
|
||||
if objAPI != nil {
|
||||
if sErr := objAPI.Shutdown(); sErr != nil {
|
||||
errorIf(err, "Unable to shutdown object API.")
|
||||
return exitFailure
|
||||
if globalShutdownCBs != nil {
|
||||
// Register the callback that should be called when the process shuts down.
|
||||
globalShutdownCBs.AddObjectLayerCB(func() errCode {
|
||||
if objAPI != nil {
|
||||
if sErr := objAPI.Shutdown(); sErr != nil {
|
||||
errorIf(err, "Unable to shutdown object API.")
|
||||
return exitFailure
|
||||
}
|
||||
}
|
||||
}
|
||||
return exitSuccess
|
||||
})
|
||||
|
||||
// Initialize a new event notifier.
|
||||
err = initEventNotifier(objAPI)
|
||||
fatalIf(err, "Unable to initialize event notification.")
|
||||
return exitSuccess
|
||||
})
|
||||
}
|
||||
|
||||
// Initialize and load bucket policies.
|
||||
err = initBucketPolicies(objAPI)
|
||||
@ -83,45 +81,28 @@ func newObjectLayer(disks, ignoredDisks []string) (ObjectLayer, error) {
|
||||
|
||||
// configureServer handler returns final handler for the http server.
|
||||
func configureServerHandler(srvCmdConfig serverCmdConfig) http.Handler {
|
||||
// Initialize storage rpc servers for every disk that is hosted on this node.
|
||||
storageRPCs, err := newRPCServer(srvCmdConfig)
|
||||
fatalIf(err, "Unable to initialize storage RPC server.")
|
||||
|
||||
// Initialize API.
|
||||
apiHandlers := objectAPIHandlers{
|
||||
ObjectAPI: newObjectLayerFn,
|
||||
}
|
||||
|
||||
// Initialize Web.
|
||||
webHandlers := &webAPIHandlers{
|
||||
ObjectAPI: newObjectLayerFn,
|
||||
}
|
||||
|
||||
// Initialize Controller.
|
||||
controllerHandlers := &controllerAPIHandlers{
|
||||
ObjectAPI: newObjectLayerFn,
|
||||
}
|
||||
|
||||
// Initialize router.
|
||||
mux := router.NewRouter()
|
||||
|
||||
// Register all routers.
|
||||
registerStorageRPCRouters(mux, storageRPCs)
|
||||
// Register storage rpc router.
|
||||
registerStorageRPCRouters(mux, srvCmdConfig)
|
||||
|
||||
// Initialize distributed NS lock.
|
||||
initDistributedNSLock(mux, srvCmdConfig)
|
||||
if isDistributedSetup(srvCmdConfig.disks) {
|
||||
registerDistNSLockRouter(mux, srvCmdConfig)
|
||||
}
|
||||
|
||||
// Register controller rpc router.
|
||||
registerControllerRPCRouter(mux, controllerHandlers)
|
||||
registerControllerRPCRouter(mux, srvCmdConfig)
|
||||
|
||||
// set environmental variable MINIO_BROWSER=off to disable minio web browser.
|
||||
// By default minio web browser is enabled.
|
||||
if !strings.EqualFold(os.Getenv("MINIO_BROWSER"), "off") {
|
||||
registerWebRouter(mux, webHandlers)
|
||||
registerWebRouter(mux)
|
||||
}
|
||||
|
||||
// Add new routers here.
|
||||
registerAPIRouter(mux, apiHandlers)
|
||||
// Add API router.
|
||||
registerAPIRouter(mux)
|
||||
|
||||
// List of some generic handlers which are applied for all incoming requests.
|
||||
var handlerFns = []HandlerFunc{
|
||||
|
@ -28,8 +28,6 @@ import (
|
||||
"github.com/minio/cli"
|
||||
)
|
||||
|
||||
var srvConfig serverCmdConfig
|
||||
|
||||
var serverFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "address",
|
||||
@ -65,6 +63,9 @@ ENVIRONMENT VARIABLES:
|
||||
MINIO_CACHE_SIZE: Set total cache size in NN[GB|MB|KB]. Defaults to 8GB.
|
||||
MINIO_CACHE_EXPIRY: Set cache expiration duration in NN[h|m|s]. Defaults to 72 hours.
|
||||
|
||||
SECURITY:
|
||||
MINIO_SECURE_CONSOLE: Set secure console to '0' to disable printing secret key. Defaults to '1'.
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio server.
|
||||
$ minio {{.Name}} /home/shared
|
||||
@ -98,6 +99,7 @@ type serverCmdConfig struct {
|
||||
serverAddr string
|
||||
disks []string
|
||||
ignoredDisks []string
|
||||
storageDisks []StorageAPI
|
||||
}
|
||||
|
||||
// getListenIPs - gets all the ips to listen on.
|
||||
@ -241,13 +243,21 @@ func checkNamingDisks(disks []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check server arguments.
|
||||
func checkServerSyntax(c *cli.Context) {
|
||||
if !c.Args().Present() || c.Args().First() == "help" {
|
||||
cli.ShowCommandHelpAndExit(c, "server", 1)
|
||||
func validateRemoteDisks(disks []StorageAPI) error {
|
||||
for _, disk := range disks {
|
||||
_, err := disk.DiskInfo()
|
||||
if _, ok := err.(*net.OpError); ok {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
disks := c.Args()
|
||||
if len(disks) > 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate input disks.
|
||||
func validateDisks(disks []string, ignoredDisks []string) []StorageAPI {
|
||||
isXL := len(disks) > 1
|
||||
if isXL {
|
||||
// Validate if input disks have duplicates in them.
|
||||
err := checkDuplicates(disks)
|
||||
fatalIf(err, "Invalid disk arguments for server.")
|
||||
@ -262,6 +272,13 @@ func checkServerSyntax(c *cli.Context) {
|
||||
err = checkNamingDisks(disks)
|
||||
fatalIf(err, "Invalid disk arguments for server.")
|
||||
}
|
||||
storageDisks, err := initStorageDisks(disks, ignoredDisks)
|
||||
fatalIf(err, "Unable to initialize storage disks.")
|
||||
if isXL {
|
||||
err = validateRemoteDisks(storageDisks)
|
||||
fatalIf(err, "Unable to validate remote disks.")
|
||||
}
|
||||
return storageDisks
|
||||
}
|
||||
|
||||
// Extract port number from address address should be of the form host:port.
|
||||
@ -296,57 +313,21 @@ func isDistributedSetup(disks []string) (isDist bool) {
|
||||
return isDist
|
||||
}
|
||||
|
||||
// Format disks before initialization object layer.
|
||||
func formatDisks(disks, ignoredDisks []string) error {
|
||||
storageDisks, err := waitForFormattingDisks(disks, ignoredDisks)
|
||||
for _, storage := range storageDisks {
|
||||
if storage == nil {
|
||||
continue
|
||||
}
|
||||
switch store := storage.(type) {
|
||||
// Closing associated TCP connections since
|
||||
// []StorageAPI is garbage collected eventually.
|
||||
case networkStorage:
|
||||
store.rpcClient.Close()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isLocalStorage(disks[0]) {
|
||||
// notify every one else that they can try init again.
|
||||
for _, storage := range storageDisks {
|
||||
switch store := storage.(type) {
|
||||
// Closing associated TCP connections since
|
||||
// []StorageAPI is garbage collected
|
||||
// eventually.
|
||||
case networkStorage:
|
||||
var reply GenericReply
|
||||
_ = store.rpcClient.Call("Storage.TryInitHandler", &GenericArgs{}, &reply)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// serverMain handler called for 'minio server' command.
|
||||
func serverMain(c *cli.Context) {
|
||||
// Check 'server' cli arguments.
|
||||
checkServerSyntax(c)
|
||||
|
||||
// Initialize server config.
|
||||
initServerConfig(c)
|
||||
|
||||
// If https.
|
||||
tls := isSSL()
|
||||
if !c.Args().Present() || c.Args().First() == "help" {
|
||||
cli.ShowCommandHelpAndExit(c, "server", 1)
|
||||
}
|
||||
|
||||
// Server address.
|
||||
serverAddress := c.String("address")
|
||||
|
||||
// Check if requested port is available.
|
||||
port := getPort(serverAddress)
|
||||
err := checkPortAvailability(port)
|
||||
fatalIf(err, "Port unavailable %d", port)
|
||||
fatalIf(checkPortAvailability(port), "Port unavailable %d", port)
|
||||
|
||||
// Saves port in a globally accessible value.
|
||||
globalMinioPort = port
|
||||
|
||||
// Disks to be ignored in server init, to skip format healing.
|
||||
ignoredDisks := strings.Split(c.String("ignore-disks"), ",")
|
||||
@ -354,8 +335,35 @@ func serverMain(c *cli.Context) {
|
||||
// Disks to be used in server init.
|
||||
disks := c.Args()
|
||||
|
||||
isDist := isDistributedSetup(disks)
|
||||
// Check 'server' cli arguments.
|
||||
storageDisks := validateDisks(disks, ignoredDisks)
|
||||
|
||||
// Initialize server config.
|
||||
initServerConfig(c)
|
||||
|
||||
// If https.
|
||||
tls := isSSL()
|
||||
|
||||
// First disk argument check if it is local.
|
||||
firstDisk := isLocalStorage(disks[0])
|
||||
|
||||
// Initialize and monitor shutdown signals.
|
||||
err := initGracefulShutdown(os.Exit)
|
||||
fatalIf(err, "Unable to initialize graceful shutdown operation")
|
||||
|
||||
// Configure server.
|
||||
srvConfig := serverCmdConfig{
|
||||
serverAddr: serverAddress,
|
||||
disks: disks,
|
||||
ignoredDisks: ignoredDisks,
|
||||
storageDisks: storageDisks,
|
||||
}
|
||||
|
||||
// Configure server.
|
||||
handler := configureServerHandler(srvConfig)
|
||||
|
||||
// Set nodes for dsync for distributed setup.
|
||||
isDist := isDistributedSetup(disks)
|
||||
if isDist {
|
||||
err = initDsyncNodes(disks, port)
|
||||
fatalIf(err, "Unable to initialize distributed locking")
|
||||
@ -364,20 +372,7 @@ func serverMain(c *cli.Context) {
|
||||
// Initialize name space lock.
|
||||
initNSLock(isDist)
|
||||
|
||||
// Configure server.
|
||||
srvConfig = serverCmdConfig{
|
||||
serverAddr: serverAddress,
|
||||
disks: disks,
|
||||
ignoredDisks: ignoredDisks,
|
||||
}
|
||||
|
||||
// Initialize and monitor shutdown signals.
|
||||
err = initGracefulShutdown(os.Exit)
|
||||
fatalIf(err, "Unable to initialize graceful shutdown operation")
|
||||
|
||||
// Configure server.
|
||||
handler := configureServerHandler(srvConfig)
|
||||
|
||||
// Initialize a new HTTP server.
|
||||
apiServer := NewServerMux(serverAddress, handler)
|
||||
|
||||
// Fetch endpoints which we are going to serve from.
|
||||
@ -405,28 +400,24 @@ func serverMain(c *cli.Context) {
|
||||
}(tls, wait)
|
||||
|
||||
// Wait for formatting of disks.
|
||||
err = formatDisks(disks, ignoredDisks)
|
||||
if err != nil {
|
||||
// FIXME: call graceful exit
|
||||
errorIf(err, "formatting storage disks failed")
|
||||
return
|
||||
}
|
||||
err = waitForFormatDisks(firstDisk, endPoints[0], storageDisks)
|
||||
fatalIf(err, "formatting storage disks failed")
|
||||
|
||||
// Once formatted, initialize object layer.
|
||||
newObject, err := newObjectLayer(disks, ignoredDisks)
|
||||
if err != nil {
|
||||
// FIXME: call graceful exit
|
||||
errorIf(err, "intializing object layer failed")
|
||||
return
|
||||
}
|
||||
|
||||
// Prints the formatted startup message.
|
||||
printStartupMessage(endPoints)
|
||||
newObject, err := newObjectLayer(storageDisks)
|
||||
fatalIf(err, "intializing object layer failed")
|
||||
|
||||
objLayerMutex.Lock()
|
||||
globalObjectAPI = newObject
|
||||
objLayerMutex.Unlock()
|
||||
|
||||
// Initialize a new event notifier.
|
||||
err = initEventNotifier(newObjectLayerFn())
|
||||
fatalIf(err, "Unable to initialize event notification.")
|
||||
|
||||
// Prints the formatted startup message once object layer is initialized.
|
||||
printStartupMessage(endPoints)
|
||||
|
||||
// Waits on the server.
|
||||
<-wait
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/cli"
|
||||
@ -167,11 +166,11 @@ func TestCheckServerSyntax(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
app.Commands = []cli.Command{serverCmd}
|
||||
serverFlagSet := flag.NewFlagSet("server", 0)
|
||||
ctx := cli.NewContext(app, serverFlagSet, nil)
|
||||
cli.NewContext(app, serverFlagSet, nil)
|
||||
disksGen := func(n int) []string {
|
||||
var disks []string
|
||||
for i := 0; i < n; i++ {
|
||||
disks = append(disks, "disk"+strconv.Itoa(i))
|
||||
disks, err := getRandomDisks(n)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialie disks %s", err)
|
||||
}
|
||||
return disks
|
||||
}
|
||||
@ -181,12 +180,13 @@ func TestCheckServerSyntax(t *testing.T) {
|
||||
disksGen(8),
|
||||
disksGen(16),
|
||||
}
|
||||
for i, test := range testCases {
|
||||
err := serverFlagSet.Parse(test)
|
||||
for i, disks := range testCases {
|
||||
err := serverFlagSet.Parse(disks)
|
||||
if err != nil {
|
||||
t.Errorf("Test %d failed to parse arguments %s", i+1, test)
|
||||
t.Errorf("Test %d failed to parse arguments %s", i+1, disks)
|
||||
}
|
||||
checkServerSyntax(ctx)
|
||||
defer removeRoots(disks)
|
||||
_ = validateDisks(disks, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,11 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/mc/pkg/console"
|
||||
)
|
||||
|
||||
@ -44,6 +46,7 @@ func printStartupMessage(endPoints []string) {
|
||||
printServerCommonMsg(endPoints)
|
||||
printCLIAccessMsg(endPoints[0])
|
||||
printObjectAPIMsg()
|
||||
printStorageInfo()
|
||||
}
|
||||
|
||||
// Prints common server startup message. Prints credential, region and browser access.
|
||||
@ -58,7 +61,11 @@ func printServerCommonMsg(endPoints []string) {
|
||||
// Colorize the message and print.
|
||||
console.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(endPointStr), 1), endPointStr)))
|
||||
console.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", cred.AccessKeyID)))
|
||||
console.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", cred.SecretAccessKey)))
|
||||
secretKey := cred.SecretAccessKey
|
||||
if os.Getenv("MINIO_SECURE_CONSOLE") == "0" {
|
||||
secretKey = "*REDACTED*"
|
||||
}
|
||||
console.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", secretKey)))
|
||||
console.Println(colorBlue("Region: ") + colorBold(fmt.Sprintf(getFormatStr(len(region), 3), region)))
|
||||
printEventNotifiers()
|
||||
|
||||
@ -90,11 +97,15 @@ func printCLIAccessMsg(endPoint string) {
|
||||
|
||||
// Configure 'mc', following block prints platform specific information for minio client.
|
||||
console.Println(colorBlue("\nCommand-line Access: ") + mcQuickStartGuide)
|
||||
secretKey := cred.SecretAccessKey
|
||||
if os.Getenv("MINIO_SECURE_CONSOLE") == "0" {
|
||||
secretKey = "*REDACTED*"
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
mcMessage := fmt.Sprintf("$ mc.exe config host add myminio %s %s %s", endPoint, cred.AccessKeyID, cred.SecretAccessKey)
|
||||
mcMessage := fmt.Sprintf("$ mc.exe config host add myminio %s %s %s", endPoint, cred.AccessKeyID, secretKey)
|
||||
console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
|
||||
} else {
|
||||
mcMessage := fmt.Sprintf("$ mc config host add myminio %s %s %s", endPoint, cred.AccessKeyID, cred.SecretAccessKey)
|
||||
mcMessage := fmt.Sprintf("$ mc config host add myminio %s %s %s", endPoint, cred.AccessKeyID, secretKey)
|
||||
console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
|
||||
}
|
||||
}
|
||||
@ -107,3 +118,24 @@ func printObjectAPIMsg() {
|
||||
console.Println(colorBlue(" Python: ") + fmt.Sprintf(getFormatStr(len(pyQuickStartGuide), 4), pyQuickStartGuide))
|
||||
console.Println(colorBlue(" JavaScript: ") + jsQuickStartGuide)
|
||||
}
|
||||
|
||||
// Get formatted disk/storage info message.
|
||||
func getStorageInfoMsg() string {
|
||||
storageInfo := newObjectLayerFn().StorageInfo()
|
||||
msg := fmt.Sprintf("%s %s Free", colorBlue("Drive Capacity:"), humanize.IBytes(uint64(storageInfo.Free)))
|
||||
diskInfo := fmt.Sprintf(" %d Online, %d Offline. We can withstand [%d] more drive failure(s).",
|
||||
storageInfo.Backend.OnlineDisks,
|
||||
storageInfo.Backend.OfflineDisks,
|
||||
storageInfo.Backend.Quorum,
|
||||
)
|
||||
if storageInfo.Backend.Type == XL {
|
||||
msg += colorBlue("\nStatus:") + fmt.Sprintf(getFormatStr(len(diskInfo), 8), diskInfo)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Prints startup message of storage capacity and erasure information.
|
||||
func printStorageInfo() {
|
||||
console.Println()
|
||||
console.Println(getStorageInfoMsg())
|
||||
}
|
||||
|
34
cmd/server-startup-msg_test.go
Normal file
34
cmd/server-startup-msg_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import "testing"
|
||||
|
||||
// Tests if we generate storage info.
|
||||
func TestStorageInfoMsg(t *testing.T) {
|
||||
obj, _, err := prepareXL()
|
||||
if err != nil {
|
||||
t.Fatal("Unable to initialize XL backend", err)
|
||||
}
|
||||
objLayerMutex.Lock()
|
||||
globalObjectAPI = obj
|
||||
objLayerMutex.Unlock()
|
||||
|
||||
if msg := getStorageInfoMsg(); msg == "" {
|
||||
t.Fatal("Empty message string is not implemented")
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Replaces any occurring '/' in string, into its encoded representation.
|
||||
func percentEncodeSlash(s string) string {
|
||||
return strings.Replace(s, "/", "%2F", -1)
|
||||
}
|
||||
|
||||
// queryEncode - encodes query values in their URL encoded form. In
|
||||
// addition to the percent encoding performed by getURLEncodedName() used
|
||||
// here, it also percent encodes '/' (forward slash)
|
||||
func queryEncode(v url.Values) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
keys := make([]string, 0, len(v))
|
||||
for k := range v {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
vs := v[k]
|
||||
prefix := percentEncodeSlash(getURLEncodedName(k)) + "="
|
||||
for _, v := range vs {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
buf.WriteString(prefix)
|
||||
buf.WriteString(percentEncodeSlash(getURLEncodedName(v)))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
@ -22,59 +22,6 @@ func TestResourceListSorting(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests validate the query encoding.
|
||||
func TestQueryEncode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
// Input.
|
||||
input url.Values
|
||||
// Expected result.
|
||||
result string
|
||||
}{
|
||||
// % should be encoded as %25
|
||||
{url.Values{
|
||||
"key": []string{"thisisthe%url"},
|
||||
}, "key=thisisthe%25url"},
|
||||
// UTF-8 encoding.
|
||||
{url.Values{
|
||||
"key": []string{"本語"},
|
||||
}, "key=%E6%9C%AC%E8%AA%9E"},
|
||||
// UTF-8 encoding with ASCII.
|
||||
{url.Values{
|
||||
"key": []string{"本語.1"},
|
||||
}, "key=%E6%9C%AC%E8%AA%9E.1"},
|
||||
// Unusual ASCII characters.
|
||||
{url.Values{
|
||||
"key": []string{">123"},
|
||||
}, "key=%3E123"},
|
||||
// Fragment path characters.
|
||||
{url.Values{
|
||||
"key": []string{"myurl#link"},
|
||||
}, "key=myurl%23link"},
|
||||
// Space should be set to %20 not '+'.
|
||||
{url.Values{
|
||||
"key": []string{"space in url"},
|
||||
}, "key=space%20in%20url"},
|
||||
// '+' shouldn't be treated as space.
|
||||
{url.Values{
|
||||
"key": []string{"url+path"},
|
||||
}, "key=url%2Bpath"},
|
||||
// '/' shouldn't be treated as '/' should be percent coded.
|
||||
{url.Values{
|
||||
"key": []string{"url/+path"},
|
||||
}, "key=url%2F%2Bpath"},
|
||||
// Values is empty and empty string.
|
||||
{nil, ""},
|
||||
}
|
||||
|
||||
// Tests generated values from url encoded name.
|
||||
for i, testCase := range testCases {
|
||||
result := queryEncode(testCase.input)
|
||||
if testCase.result != result {
|
||||
t.Errorf("Test %d: Expected queryEncoded result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoesPresignedV2SignatureMatch(t *testing.T) {
|
||||
root, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
|
@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// signVerifyReader represents an io.Reader compatible interface which
|
||||
// transparently calculates sha256, caller should call `Verify()` to
|
||||
// validate the signature header.
|
||||
type signVerifyReader struct {
|
||||
Request *http.Request // HTTP request to be validated and read.
|
||||
HashWriter hash.Hash // sha256 hash writer.
|
||||
}
|
||||
|
||||
// Initializes a new signature verify reader.
|
||||
func newSignVerify(req *http.Request) *signVerifyReader {
|
||||
return &signVerifyReader{
|
||||
Request: req, // Save the request.
|
||||
HashWriter: sha256.New(), // Inititalize sha256.
|
||||
}
|
||||
}
|
||||
|
||||
// isSignVerify - is given reader a `signVerifyReader`.
|
||||
func isSignVerify(reader io.Reader) bool {
|
||||
_, ok := reader.(*signVerifyReader)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Verify - verifies signature and returns error upon signature mismatch.
|
||||
func (v *signVerifyReader) Verify() error {
|
||||
region := serverConfig.GetRegion()
|
||||
shaPayloadHex := hex.EncodeToString(v.HashWriter.Sum(nil))
|
||||
if skipContentSha256Cksum(v.Request) {
|
||||
// Sets 'UNSIGNED-PAYLOAD' if client requested to not calculated sha256.
|
||||
shaPayloadHex = unsignedPayload
|
||||
}
|
||||
// Signature verification block.
|
||||
var s3Error APIErrorCode
|
||||
if isRequestSignatureV4(v.Request) {
|
||||
s3Error = doesSignatureMatch(shaPayloadHex, v.Request, region)
|
||||
} else if isRequestPresignedSignatureV4(v.Request) {
|
||||
s3Error = doesPresignedSignatureMatch(shaPayloadHex, v.Request, region)
|
||||
} else {
|
||||
// Couldn't figure out the request type, set the error as AccessDenied.
|
||||
s3Error = ErrAccessDenied
|
||||
}
|
||||
// Set signature error as 'errSignatureMismatch' if possible.
|
||||
var sErr error
|
||||
// Validate if we have received signature mismatch or sha256 mismatch.
|
||||
if s3Error != ErrNone {
|
||||
switch s3Error {
|
||||
case ErrContentSHA256Mismatch:
|
||||
sErr = errContentSHA256Mismatch
|
||||
case ErrSignatureDoesNotMatch:
|
||||
sErr = errSignatureMismatch
|
||||
default:
|
||||
sErr = fmt.Errorf("%v", getAPIError(s3Error))
|
||||
}
|
||||
return sErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reads from request body and writes to hash writer. All reads performed
|
||||
// through it are matched with corresponding writes to hash writer. There is
|
||||
// no internal buffering the write must complete before the read completes.
|
||||
// Any error encountered while writing is reported as a read error. As a
|
||||
// special case `Read()` skips writing to hash writer if the client requested
|
||||
// for the payload to be skipped.
|
||||
func (v *signVerifyReader) Read(b []byte) (n int, err error) {
|
||||
n, err = v.Request.Body.Read(b)
|
||||
if n > 0 {
|
||||
// Skip calculating the hash.
|
||||
if skipContentSha256Cksum(v.Request) {
|
||||
return
|
||||
}
|
||||
// Stagger all reads to its corresponding writes to hash writer.
|
||||
if n, err = v.HashWriter.Write(b[:n]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -59,6 +59,3 @@ var errVolumeAccessDenied = errors.New("volume access denied")
|
||||
|
||||
// errVolumeAccessDenied - cannot access file, insufficient permissions.
|
||||
var errFileAccessDenied = errors.New("file access denied")
|
||||
|
||||
// errVolumeBusy - remote disk is not connected to yet.
|
||||
var errVolumeBusy = errors.New("volume is busy")
|
||||
|
@ -20,6 +20,9 @@ import "github.com/minio/minio/pkg/disk"
|
||||
|
||||
// StorageAPI interface.
|
||||
type StorageAPI interface {
|
||||
// Stringified version of disk.
|
||||
String() string
|
||||
|
||||
// Storage operations.
|
||||
DiskInfo() (info disk.Info, err error)
|
||||
|
||||
|
@ -100,8 +100,7 @@ func newRPCClient(networkPath string) (StorageAPI, error) {
|
||||
|
||||
// Dial minio rpc storage http path.
|
||||
rpcPath := path.Join(storageRPCPath, netPath)
|
||||
port := getPort(srvConfig.serverAddr)
|
||||
rpcAddr := netAddr + ":" + strconv.Itoa(port)
|
||||
rpcAddr := netAddr + ":" + strconv.Itoa(globalMinioPort)
|
||||
// Initialize rpc client with network address and rpc path.
|
||||
cred := serverConfig.GetCredential()
|
||||
rpcClient := newAuthClient(&authConfig{
|
||||
@ -123,6 +122,11 @@ func newRPCClient(networkPath string) (StorageAPI, error) {
|
||||
return ndisk, nil
|
||||
}
|
||||
|
||||
// Stringer interface compatible representation of network device.
|
||||
func (n networkStorage) String() string {
|
||||
return n.netAddr + ":" + n.netPath
|
||||
}
|
||||
|
||||
// DiskInfo - fetch disk information for a remote disk.
|
||||
func (n networkStorage) DiskInfo() (info disk.Info, err error) {
|
||||
args := GenericArgs{}
|
||||
|
114
cmd/storage-rpc-client_test.go
Normal file
114
cmd/storage-rpc-client_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests storage error transformation.
|
||||
func TestStorageErr(t *testing.T) {
|
||||
unknownErr := errors.New("Unknown error")
|
||||
testCases := []struct {
|
||||
expectedErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
expectedErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
expectedErr: io.EOF,
|
||||
err: fmt.Errorf("%s", io.EOF.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: io.ErrUnexpectedEOF,
|
||||
err: fmt.Errorf("%s", io.ErrUnexpectedEOF.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errDiskNotFound,
|
||||
err: &net.OpError{},
|
||||
},
|
||||
{
|
||||
expectedErr: errDiskNotFound,
|
||||
err: rpc.ErrShutdown,
|
||||
},
|
||||
{
|
||||
expectedErr: errUnexpected,
|
||||
err: fmt.Errorf("%s", errUnexpected.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errDiskFull,
|
||||
err: fmt.Errorf("%s", errDiskFull.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errVolumeNotFound,
|
||||
err: fmt.Errorf("%s", errVolumeNotFound.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errVolumeExists,
|
||||
err: fmt.Errorf("%s", errVolumeExists.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errFileNotFound,
|
||||
err: fmt.Errorf("%s", errFileNotFound.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errFileAccessDenied,
|
||||
err: fmt.Errorf("%s", errFileAccessDenied.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errIsNotRegular,
|
||||
err: fmt.Errorf("%s", errIsNotRegular.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errVolumeNotEmpty,
|
||||
err: fmt.Errorf("%s", errVolumeNotEmpty.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errVolumeAccessDenied,
|
||||
err: fmt.Errorf("%s", errVolumeAccessDenied.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errCorruptedFormat,
|
||||
err: fmt.Errorf("%s", errCorruptedFormat.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errUnformattedDisk,
|
||||
err: fmt.Errorf("%s", errUnformattedDisk.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: errFileNameTooLong,
|
||||
err: fmt.Errorf("%s", errFileNameTooLong.Error()),
|
||||
},
|
||||
{
|
||||
expectedErr: unknownErr,
|
||||
err: unknownErr,
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
resultErr := toStorageErr(testCase.err)
|
||||
if testCase.expectedErr != resultErr {
|
||||
t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.expectedErr, resultErr)
|
||||
}
|
||||
}
|
||||
}
|
@ -205,6 +205,18 @@ func (s *storageServer) RenameFileHandler(args *RenameFileArgs, reply *GenericRe
|
||||
return s.storage.RenameFile(args.SrcVol, args.SrcPath, args.DstVol, args.DstPath)
|
||||
}
|
||||
|
||||
// TryInitHandler - wake up storage server.
|
||||
func (s *storageServer) TryInitHandler(args *GenericArgs, reply *GenericReply) error {
|
||||
if !isRPCTokenValid(args.Token) {
|
||||
return errInvalidToken
|
||||
}
|
||||
go func() {
|
||||
globalWakeupCh <- struct{}{}
|
||||
}()
|
||||
*reply = GenericReply{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize new storage rpc.
|
||||
func newRPCServer(serverConfig serverCmdConfig) (servers []*storageServer, err error) {
|
||||
// Initialize posix storage API.
|
||||
@ -245,9 +257,13 @@ func newRPCServer(serverConfig serverCmdConfig) (servers []*storageServer, err e
|
||||
}
|
||||
|
||||
// registerStorageRPCRouter - register storage rpc router.
|
||||
func registerStorageRPCRouters(mux *router.Router, stServers []*storageServer) {
|
||||
func registerStorageRPCRouters(mux *router.Router, srvCmdConfig serverCmdConfig) {
|
||||
// Initialize storage rpc servers for every disk that is hosted on this node.
|
||||
storageRPCs, err := newRPCServer(srvCmdConfig)
|
||||
fatalIf(err, "Unable to initialize storage RPC server.")
|
||||
|
||||
// Create a unique route for each disk exported from this node.
|
||||
for _, stServer := range stServers {
|
||||
for _, stServer := range storageRPCs {
|
||||
storageRPCServer := rpc.NewServer()
|
||||
storageRPCServer.RegisterName("Storage", stServer)
|
||||
// Add minio storage routes.
|
||||
|
@ -40,6 +40,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
router "github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@ -48,6 +49,9 @@ func init() {
|
||||
// Initialize name space lock.
|
||||
isDist := false
|
||||
initNSLock(isDist)
|
||||
|
||||
// Disable printing console messages during tests.
|
||||
color.Output = ioutil.Discard
|
||||
}
|
||||
|
||||
func prepareFS() (ObjectLayer, string, error) {
|
||||
@ -55,7 +59,7 @@ func prepareFS() (ObjectLayer, string, error) {
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
obj, err := getSingleNodeObjectLayer(fsDirs[0])
|
||||
obj, _, err := initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
removeRoots(fsDirs)
|
||||
return nil, "", err
|
||||
@ -69,7 +73,7 @@ func prepareXL() (ObjectLayer, []string, error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
obj, err := getXLObjectLayer(fsDirs, nil)
|
||||
obj, _, err := initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
removeRoots(fsDirs)
|
||||
return nil, nil, err
|
||||
@ -170,13 +174,20 @@ func StartTestServer(t TestErrHandler, instanceType string) TestServer {
|
||||
testServer.Disks = disks
|
||||
testServer.AccessKey = credentials.AccessKeyID
|
||||
testServer.SecretKey = credentials.SecretAccessKey
|
||||
// Run TestServer.
|
||||
testServer.Server = httptest.NewServer(configureServerHandler(serverCmdConfig{disks: disks}))
|
||||
|
||||
objLayer, err := makeTestBackend(disks, instanceType)
|
||||
objLayer, storageDisks, err := initObjectLayer(disks, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed obtaining Temp Backend: <ERROR> %s", err)
|
||||
}
|
||||
|
||||
// Run TestServer.
|
||||
testServer.Server = httptest.NewServer(configureServerHandler(
|
||||
serverCmdConfig{
|
||||
disks: disks,
|
||||
storageDisks: storageDisks,
|
||||
},
|
||||
))
|
||||
|
||||
testServer.Obj = objLayer
|
||||
objLayerMutex.Lock()
|
||||
globalObjectAPI = objLayer
|
||||
@ -186,16 +197,10 @@ func StartTestServer(t TestErrHandler, instanceType string) TestServer {
|
||||
|
||||
// Initializes control RPC end points.
|
||||
// The object Layer will be a temp back used for testing purpose.
|
||||
func initTestControlRPCEndPoint(objectLayer ObjectLayer) http.Handler {
|
||||
// Initialize Web.
|
||||
|
||||
controllerHandlers := &controllerAPIHandlers{
|
||||
ObjectAPI: func() ObjectLayer { return objectLayer },
|
||||
}
|
||||
|
||||
func initTestControlRPCEndPoint(srvCmdConfig serverCmdConfig) http.Handler {
|
||||
// Initialize router.
|
||||
muxRouter := router.NewRouter()
|
||||
registerControllerRPCRouter(muxRouter, controllerHandlers)
|
||||
registerControllerRPCRouter(muxRouter, srvCmdConfig)
|
||||
return muxRouter
|
||||
}
|
||||
|
||||
@ -208,20 +213,14 @@ func StartTestRPCServer(t TestErrHandler, instanceType string) TestServer {
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create disks for the backend")
|
||||
}
|
||||
// create an instance of TestServer.
|
||||
testRPCServer := TestServer{}
|
||||
// create temporary backend for the test server.
|
||||
objLayer, err := makeTestBackend(disks, instanceType)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed obtaining Temp Backend: <ERROR> %s", err)
|
||||
}
|
||||
|
||||
root, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
// create an instance of TestServer.
|
||||
testRPCServer := TestServer{}
|
||||
// Get credential.
|
||||
credentials := serverConfig.GetCredential()
|
||||
|
||||
@ -229,9 +228,21 @@ func StartTestRPCServer(t TestErrHandler, instanceType string) TestServer {
|
||||
testRPCServer.Disks = disks
|
||||
testRPCServer.AccessKey = credentials.AccessKeyID
|
||||
testRPCServer.SecretKey = credentials.SecretAccessKey
|
||||
testRPCServer.Obj = objLayer
|
||||
|
||||
// create temporary backend for the test server.
|
||||
objLayer, storageDisks, err := initObjectLayer(disks, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed obtaining Temp Backend: <ERROR> %s", err)
|
||||
}
|
||||
|
||||
objLayerMutex.Lock()
|
||||
globalObjectAPI = objLayer
|
||||
objLayerMutex.Unlock()
|
||||
|
||||
// Run TestServer.
|
||||
testRPCServer.Server = httptest.NewServer(initTestControlRPCEndPoint(objLayer))
|
||||
testRPCServer.Server = httptest.NewServer(initTestControlRPCEndPoint(serverCmdConfig{
|
||||
storageDisks: storageDisks,
|
||||
}))
|
||||
|
||||
return testRPCServer
|
||||
}
|
||||
@ -476,6 +487,38 @@ func newTestStreamingSignedRequest(method, urlStr string, contentLength, chunkSi
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Replaces any occurring '/' in string, into its encoded representation.
|
||||
func percentEncodeSlash(s string) string {
|
||||
return strings.Replace(s, "/", "%2F", -1)
|
||||
}
|
||||
|
||||
// queryEncode - encodes query values in their URL encoded form. In
|
||||
// addition to the percent encoding performed by getURLEncodedName() used
|
||||
// here, it also percent encodes '/' (forward slash)
|
||||
func queryEncode(v url.Values) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
keys := make([]string, 0, len(v))
|
||||
for k := range v {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
vs := v[k]
|
||||
prefix := percentEncodeSlash(getURLEncodedName(k)) + "="
|
||||
for _, v := range vs {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
buf.WriteString(prefix)
|
||||
buf.WriteString(percentEncodeSlash(getURLEncodedName(v)))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -860,31 +903,6 @@ func getTestWebRPCResponse(resp *httptest.ResponseRecorder, data interface{}) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// creates the temp backend setup.
|
||||
// if the option is
|
||||
// FS: Returns a temp single disk setup initializes FS Backend.
|
||||
// XL: Returns a 16 temp single disk setup and initializse XL Backend.
|
||||
func makeTestBackend(disks []string, instanceType string) (ObjectLayer, error) {
|
||||
switch instanceType {
|
||||
case "FS":
|
||||
objLayer, err := getSingleNodeObjectLayer(disks[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return objLayer, err
|
||||
|
||||
case "XL":
|
||||
objectLayer, err := getXLObjectLayer(disks, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return objectLayer, err
|
||||
default:
|
||||
errMsg := "Invalid instance type, Only FS and XL are valid options"
|
||||
return nil, fmt.Errorf("Failed obtaining Temp XL layer: <ERROR> %s", errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
var src = rand.NewSource(time.Now().UTC().UnixNano())
|
||||
|
||||
// Function to generate random string for bucket/object names.
|
||||
@ -1216,33 +1234,31 @@ func getRandomDisks(N int) ([]string, error) {
|
||||
return erasureDisks, nil
|
||||
}
|
||||
|
||||
// getXLObjectLayer - Instantiates XL object layer and returns it.
|
||||
func getXLObjectLayer(erasureDisks []string, ignoredDisks []string) (ObjectLayer, error) {
|
||||
err := formatDisks(erasureDisks, ignoredDisks)
|
||||
// initObjectLayer - Instantiates object layer and returns it.
|
||||
func initObjectLayer(disks []string, ignoredDisks []string) (ObjectLayer, []StorageAPI, error) {
|
||||
storageDisks, err := initStorageDisks(disks, ignoredDisks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
objLayer, err := newXLObjects(erasureDisks, ignoredDisks)
|
||||
|
||||
err = waitForFormatDisks(true, "", storageDisks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
objLayer, err := newObjectLayer(storageDisks)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Disabling the cache for integration tests.
|
||||
// Should use the object layer tests for validating cache.
|
||||
if xl, ok := objLayer.(xlObjects); ok {
|
||||
xl.objCacheEnabled = false
|
||||
}
|
||||
|
||||
return objLayer, nil
|
||||
}
|
||||
|
||||
// getSingleNodeObjectLayer - Instantiates single node object layer and returns it.
|
||||
func getSingleNodeObjectLayer(disk string) (ObjectLayer, error) {
|
||||
// Create the object layer.
|
||||
objLayer, err := newFSObjects(disk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return objLayer, nil
|
||||
// Success.
|
||||
return objLayer, storageDisks, nil
|
||||
}
|
||||
|
||||
// removeRoots - Cleans up initialized directories during tests.
|
||||
@ -1262,6 +1278,48 @@ func removeDiskN(disks []string, n int) {
|
||||
}
|
||||
}
|
||||
|
||||
// Makes a entire new copy of a StorageAPI slice.
|
||||
func deepCopyStorageDisks(storageDisks []StorageAPI) []StorageAPI {
|
||||
newStorageDisks := make([]StorageAPI, len(storageDisks))
|
||||
for i, disk := range storageDisks {
|
||||
newStorageDisks[i] = disk
|
||||
}
|
||||
return newStorageDisks
|
||||
}
|
||||
|
||||
// Initializes storage disks with 'N' errored disks, N disks return 'err' for each disk access.
|
||||
func prepareNErroredDisks(storageDisks []StorageAPI, offline int, err error, t *testing.T) []StorageAPI {
|
||||
if offline > len(storageDisks) {
|
||||
t.Fatal("Requested more offline disks than supplied storageDisks slice", offline, len(storageDisks))
|
||||
}
|
||||
|
||||
for i := 0; i < offline; i++ {
|
||||
d := storageDisks[i].(*posix)
|
||||
storageDisks[i] = &naughtyDisk{disk: d, defaultErr: err}
|
||||
}
|
||||
return storageDisks
|
||||
}
|
||||
|
||||
// Initializes storage disks with 'N' offline disks, N disks returns 'errDiskNotFound' for each disk access.
|
||||
func prepareNOfflineDisks(storageDisks []StorageAPI, offline int, t *testing.T) []StorageAPI {
|
||||
return prepareNErroredDisks(storageDisks, offline, errDiskNotFound, t)
|
||||
}
|
||||
|
||||
// Initializes backend storage disks.
|
||||
func prepareXLStorageDisks(t *testing.T) ([]StorageAPI, []string) {
|
||||
nDisks := 16
|
||||
fsDirs, err := getRandomDisks(nDisks)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
_, storageDisks, err := initObjectLayer(fsDirs, nil)
|
||||
if err != nil {
|
||||
removeRoots(fsDirs)
|
||||
t.Fatal("Unable to initialize storage disks", err)
|
||||
}
|
||||
return storageDisks, fsDirs
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -1370,7 +1428,7 @@ func ExecObjectLayerStaleFilesTest(t *testing.T, objTest objTestStaleFilesType)
|
||||
if err != nil {
|
||||
t.Fatalf("Initialization of disks for XL setup: %s", err)
|
||||
}
|
||||
objLayer, err := getXLObjectLayer(erasureDisks, nil)
|
||||
objLayer, _, err := initObjectLayer(erasureDisks, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Initialization of object layer failed for XL setup: %s", err)
|
||||
}
|
||||
@ -1379,105 +1437,79 @@ func ExecObjectLayerStaleFilesTest(t *testing.T, objTest objTestStaleFilesType)
|
||||
defer removeRoots(erasureDisks)
|
||||
}
|
||||
|
||||
// addAPIFunc helper function to add API functions identified by name to the routers.
|
||||
func addAPIFunc(muxRouter *router.Router, apiRouter *router.Router, bucket *router.Router,
|
||||
api objectAPIHandlers, apiFunction string) {
|
||||
switch apiFunction {
|
||||
// Register ListBuckets handler.
|
||||
case "ListBuckets":
|
||||
apiRouter.Methods("GET").HandlerFunc(api.ListBucketsHandler)
|
||||
// Register GetObject handler.
|
||||
case "GetObject":
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.GetObjectHandler)
|
||||
// Register PutObject handler.
|
||||
case "PutObject":
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectHandler)
|
||||
// Register Delete Object handler.
|
||||
case "DeleteObject":
|
||||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.DeleteObjectHandler)
|
||||
// Register Copy Object handler.
|
||||
case "CopyObject":
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectHandler)
|
||||
// Register PutBucket Policy handler.
|
||||
case "PutBucketPolicy":
|
||||
bucket.Methods("PUT").HandlerFunc(api.PutBucketPolicyHandler).Queries("policy", "")
|
||||
// Register Delete bucket HTTP policy handler.
|
||||
case "DeleteBucketPolicy":
|
||||
bucket.Methods("DELETE").HandlerFunc(api.DeleteBucketPolicyHandler).Queries("policy", "")
|
||||
// Register Get Bucket policy HTTP Handler.
|
||||
case "GetBucketPolicy":
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketPolicyHandler).Queries("policy", "")
|
||||
// Register GetBucketLocation handler.
|
||||
case "GetBucketLocation":
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketLocationHandler).Queries("location", "")
|
||||
// Register HeadBucket handler.
|
||||
case "HeadBucket":
|
||||
bucket.Methods("HEAD").HandlerFunc(api.HeadBucketHandler)
|
||||
// Register New Multipart upload handler.
|
||||
case "NewMultipart":
|
||||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.NewMultipartUploadHandler).Queries("uploads", "")
|
||||
// Register PutObjectPart handler.
|
||||
case "PutObjectPart":
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
// Register ListObjectParts handler.
|
||||
case "ListObjectParts":
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
// Register ListMultipartUploads handler.
|
||||
case "ListMultipartUploads":
|
||||
bucket.Methods("GET").HandlerFunc(api.ListMultipartUploadsHandler).Queries("uploads", "")
|
||||
// Register Complete Multipart Upload handler.
|
||||
case "CompleteMultipart":
|
||||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
// Register GetBucketNotification Handler.
|
||||
case "GetBucketNotification":
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketNotificationHandler).Queries("notification", "")
|
||||
// Register PutBucketNotification Handler.
|
||||
case "PutBucketNotification":
|
||||
bucket.Methods("PUT").HandlerFunc(api.PutBucketNotificationHandler).Queries("notification", "")
|
||||
// Register ListenBucketNotification Handler.
|
||||
case "ListenBucketNotification":
|
||||
bucket.Methods("GET").HandlerFunc(api.ListenBucketNotificationHandler).Queries("events", "{events:.*}")
|
||||
// Register all api endpoints by default.
|
||||
default:
|
||||
registerAPIRouter(muxRouter, api)
|
||||
// No need to register any more end points, all the end points are registered.
|
||||
break
|
||||
func registerBucketLevelFunc(bucket *router.Router, api objectAPIHandlers, apiFunctions ...string) {
|
||||
for _, apiFunction := range apiFunctions {
|
||||
switch apiFunction {
|
||||
case "PostPolicy":
|
||||
// Register PostPolicy handler.
|
||||
bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(api.PostPolicyBucketHandler)
|
||||
// Register GetObject handler.
|
||||
case "GetObject":
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.GetObjectHandler)
|
||||
// Register PutObject handler.
|
||||
case "PutObject":
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectHandler)
|
||||
// Register Delete Object handler.
|
||||
case "DeleteObject":
|
||||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.DeleteObjectHandler)
|
||||
// Register Copy Object handler.
|
||||
case "CopyObject":
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectHandler)
|
||||
// Register PutBucket Policy handler.
|
||||
case "PutBucketPolicy":
|
||||
bucket.Methods("PUT").HandlerFunc(api.PutBucketPolicyHandler).Queries("policy", "")
|
||||
// Register Delete bucket HTTP policy handler.
|
||||
case "DeleteBucketPolicy":
|
||||
bucket.Methods("DELETE").HandlerFunc(api.DeleteBucketPolicyHandler).Queries("policy", "")
|
||||
// Register Get Bucket policy HTTP Handler.
|
||||
case "GetBucketPolicy":
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketPolicyHandler).Queries("policy", "")
|
||||
// Register GetBucketLocation handler.
|
||||
case "GetBucketLocation":
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketLocationHandler).Queries("location", "")
|
||||
// Register HeadBucket handler.
|
||||
case "HeadBucket":
|
||||
bucket.Methods("HEAD").HandlerFunc(api.HeadBucketHandler)
|
||||
// Register New Multipart upload handler.
|
||||
case "NewMultipart":
|
||||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.NewMultipartUploadHandler).Queries("uploads", "")
|
||||
// Register PutObjectPart handler.
|
||||
case "PutObjectPart":
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
// Register ListObjectParts handler.
|
||||
case "ListObjectParts":
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
// Register ListMultipartUploads handler.
|
||||
case "ListMultipartUploads":
|
||||
bucket.Methods("GET").HandlerFunc(api.ListMultipartUploadsHandler).Queries("uploads", "")
|
||||
// Register Complete Multipart Upload handler.
|
||||
case "CompleteMultipart":
|
||||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}")
|
||||
// Register GetBucketNotification Handler.
|
||||
case "GetBucketNotification":
|
||||
bucket.Methods("GET").HandlerFunc(api.GetBucketNotificationHandler).Queries("notification", "")
|
||||
// Register PutBucketNotification Handler.
|
||||
case "PutBucketNotification":
|
||||
bucket.Methods("PUT").HandlerFunc(api.PutBucketNotificationHandler).Queries("notification", "")
|
||||
// Register ListenBucketNotification Handler.
|
||||
case "ListenBucketNotification":
|
||||
bucket.Methods("GET").HandlerFunc(api.ListenBucketNotificationHandler).Queries("events", "{events:.*}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a http.Handler capable of routing API requests to handlers corresponding to apiFunctions,
|
||||
// with ObjectAPI set to nil.
|
||||
func initTestNilObjAPIEndPoints(apiFunctions []string) http.Handler {
|
||||
muxRouter := router.NewRouter()
|
||||
// All object storage operations are registered as HTTP handlers on `objectAPIHandlers`.
|
||||
// When the handlers get a HTTP request they use the underlyting ObjectLayer to perform operations.
|
||||
nilAPI := objectAPIHandlers{
|
||||
ObjectAPI: func() ObjectLayer {
|
||||
objLayerMutex.Lock()
|
||||
defer objLayerMutex.Unlock()
|
||||
globalObjectAPI = nil
|
||||
return globalObjectAPI
|
||||
},
|
||||
// registerAPIFunctions helper function to add API functions identified by name to the routers.
|
||||
func registerAPIFunctions(muxRouter *router.Router, objLayer ObjectLayer, apiFunctions ...string) {
|
||||
if len(apiFunctions) == 0 {
|
||||
// Register all api endpoints by default.
|
||||
registerAPIRouter(muxRouter)
|
||||
return
|
||||
}
|
||||
|
||||
// API Router.
|
||||
apiRouter := muxRouter.NewRoute().PathPrefix("/").Subrouter()
|
||||
// Bucket router.
|
||||
bucket := apiRouter.PathPrefix("/{bucket}").Subrouter()
|
||||
// Iterate the list of API functions requested for and register them in mux HTTP handler.
|
||||
for _, apiFunction := range apiFunctions {
|
||||
addAPIFunc(muxRouter, apiRouter, bucket, nilAPI, apiFunction)
|
||||
}
|
||||
return muxRouter
|
||||
}
|
||||
bucketRouter := apiRouter.PathPrefix("/{bucket}").Subrouter()
|
||||
|
||||
// Takes in XL/FS object layer, and the list of API end points to be tested/required, registers the API end points and returns the HTTP handler.
|
||||
// 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.
|
||||
muxRouter := router.NewRouter()
|
||||
// All object storage operations are registered as HTTP handlers on `objectAPIHandlers`.
|
||||
// When the handlers get a HTTP request they use the underlyting ObjectLayer to perform operations.
|
||||
objLayerMutex.Lock()
|
||||
@ -1488,26 +1520,49 @@ func initTestAPIEndPoints(objLayer ObjectLayer, apiFunctions []string) http.Hand
|
||||
ObjectAPI: newObjectLayerFn,
|
||||
}
|
||||
|
||||
// API Router.
|
||||
apiRouter := muxRouter.NewRoute().PathPrefix("/").Subrouter()
|
||||
// Bucket router.
|
||||
bucket := apiRouter.PathPrefix("/{bucket}").Subrouter()
|
||||
// Iterate the list of API functions requested for and register them in mux HTTP handler.
|
||||
for _, apiFunction := range apiFunctions {
|
||||
addAPIFunc(muxRouter, apiRouter, bucket, api, apiFunction)
|
||||
// Register ListBuckets handler.
|
||||
apiRouter.Methods("GET").HandlerFunc(api.ListBucketsHandler)
|
||||
// Register all bucket level handlers.
|
||||
registerBucketLevelFunc(bucketRouter, api, apiFunctions...)
|
||||
}
|
||||
|
||||
// Returns a http.Handler capable of routing API requests to handlers corresponding to apiFunctions,
|
||||
// with ObjectAPI set to nil.
|
||||
func initTestNilObjAPIEndPoints(apiFunctions []string) http.Handler {
|
||||
muxRouter := router.NewRouter()
|
||||
if len(apiFunctions) > 0 {
|
||||
// Iterate the list of API functions requested for and register them in mux HTTP handler.
|
||||
registerAPIFunctions(muxRouter, nil, apiFunctions...)
|
||||
return muxRouter
|
||||
}
|
||||
registerAPIRouter(muxRouter)
|
||||
return muxRouter
|
||||
}
|
||||
|
||||
// Takes in XL/FS object layer, and the list of API end points to be tested/required, registers the API end points and returns the HTTP handler.
|
||||
// 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.
|
||||
muxRouter := router.NewRouter()
|
||||
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
|
||||
}
|
||||
registerAPIRouter(muxRouter)
|
||||
return muxRouter
|
||||
}
|
||||
|
||||
// Initialize Web RPC Handlers for testing
|
||||
func initTestWebRPCEndPoint(objLayer ObjectLayer) http.Handler {
|
||||
// Initialize Web.
|
||||
webHandlers := &webAPIHandlers{
|
||||
ObjectAPI: func() ObjectLayer { return objLayer },
|
||||
}
|
||||
objLayerMutex.Lock()
|
||||
globalObjectAPI = objLayer
|
||||
objLayerMutex.Unlock()
|
||||
|
||||
// Initialize router.
|
||||
muxRouter := router.NewRouter()
|
||||
registerWebRouter(muxRouter, webHandlers)
|
||||
registerWebRouter(muxRouter)
|
||||
return muxRouter
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
router "github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
)
|
||||
|
||||
@ -1016,9 +1015,9 @@ func testWebSetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE
|
||||
// TestWebCheckAuthorization - Test Authorization for all web handlers
|
||||
func TestWebCheckAuthorization(t *testing.T) {
|
||||
// Prepare XL backend
|
||||
obj, fsDirs, e := prepareXL()
|
||||
if e != nil {
|
||||
t.Fatalf("Initialization of object layer failed for XL setup: %s", e)
|
||||
obj, fsDirs, err := prepareXL()
|
||||
if err != nil {
|
||||
t.Fatalf("Initialization of object layer failed for XL setup: %s", err)
|
||||
}
|
||||
// Executing the object layer tests for XL.
|
||||
defer removeRoots(fsDirs)
|
||||
@ -1027,9 +1026,9 @@ func TestWebCheckAuthorization(t *testing.T) {
|
||||
apiRouter := initTestWebRPCEndPoint(obj)
|
||||
// initialize the server and obtain the credentials and root.
|
||||
// credentials are necessary to sign the HTTP request.
|
||||
rootPath, e := newTestConfig("us-east-1")
|
||||
if e != nil {
|
||||
t.Fatalf("Init Test config failed")
|
||||
rootPath, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatal("Init Test config failed", err)
|
||||
}
|
||||
// remove the root folder after the test ends.
|
||||
defer removeAll(rootPath)
|
||||
@ -1042,9 +1041,9 @@ func TestWebCheckAuthorization(t *testing.T) {
|
||||
for _, rpcCall := range webRPCs {
|
||||
args := &GenericArgs{}
|
||||
reply := &WebGenericRep{}
|
||||
req, err := newTestWebRPCRequest("Web."+rpcCall, "Bearer fooauthorization", args)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request: <ERROR> %v", rpcCall, err)
|
||||
req, nerr := newTestWebRPCRequest("Web."+rpcCall, "Bearer fooauthorization", args)
|
||||
if nerr != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request: <ERROR> %v", rpcCall, nerr)
|
||||
}
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
@ -1097,18 +1096,14 @@ func TestWebCheckAuthorization(t *testing.T) {
|
||||
|
||||
// TestWebObjectLayerNotReady - Test RPCs responses when disks are not ready
|
||||
func TestWebObjectLayerNotReady(t *testing.T) {
|
||||
webHandlers := &webAPIHandlers{
|
||||
ObjectAPI: func() ObjectLayer { return nil },
|
||||
}
|
||||
// Initialize router.
|
||||
apiRouter := router.NewRouter()
|
||||
registerWebRouter(apiRouter, webHandlers)
|
||||
// Initialize web rpc endpoint.
|
||||
apiRouter := initTestWebRPCEndPoint(nil)
|
||||
|
||||
// initialize the server and obtain the credentials and root.
|
||||
// credentials are necessary to sign the HTTP request.
|
||||
rootPath, e := newTestConfig("us-east-1")
|
||||
if e != nil {
|
||||
t.Fatalf("Init Test config failed")
|
||||
rootPath, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatal("Init Test config failed", err)
|
||||
}
|
||||
// remove the root folder after the test ends.
|
||||
defer removeAll(rootPath)
|
||||
@ -1116,9 +1111,9 @@ func TestWebObjectLayerNotReady(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
credentials := serverConfig.GetCredential()
|
||||
authorization, e := getWebRPCToken(apiRouter, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if e != nil {
|
||||
t.Fatal("Cannot authenticate")
|
||||
authorization, err := getWebRPCToken(apiRouter, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot authenticate", err)
|
||||
}
|
||||
|
||||
// Check if web rpc calls return Server not initialized. ServerInfo, GenerateAuth,
|
||||
@ -1128,9 +1123,9 @@ func TestWebObjectLayerNotReady(t *testing.T) {
|
||||
for _, rpcCall := range webRPCs {
|
||||
args := &GenericArgs{}
|
||||
reply := &WebGenericRep{}
|
||||
req, err := newTestWebRPCRequest("Web."+rpcCall, authorization, args)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request: <ERROR> %v", rpcCall, err)
|
||||
req, nerr := newTestWebRPCRequest("Web."+rpcCall, authorization, args)
|
||||
if nerr != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request: <ERROR> %v", rpcCall, nerr)
|
||||
}
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
@ -1184,9 +1179,9 @@ func TestWebObjectLayerNotReady(t *testing.T) {
|
||||
// TestWebObjectLayerFaultyDisks - Test Web RPC responses with faulty disks
|
||||
func TestWebObjectLayerFaultyDisks(t *testing.T) {
|
||||
// Prepare XL backend
|
||||
obj, fsDirs, e := prepareXL()
|
||||
if e != nil {
|
||||
t.Fatalf("Initialization of object layer failed for XL setup: %s", e)
|
||||
obj, fsDirs, err := prepareXL()
|
||||
if err != nil {
|
||||
t.Fatalf("Initialization of object layer failed for XL setup: %s", err)
|
||||
}
|
||||
// Executing the object layer tests for XL.
|
||||
defer removeRoots(fsDirs)
|
||||
@ -1197,18 +1192,14 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) {
|
||||
xl.storageDisks[i] = newNaughtyDisk(d.(*posix), nil, errFaultyDisk)
|
||||
}
|
||||
|
||||
webHandlers := &webAPIHandlers{
|
||||
ObjectAPI: func() ObjectLayer { return obj },
|
||||
}
|
||||
// Initialize router.
|
||||
apiRouter := router.NewRouter()
|
||||
registerWebRouter(apiRouter, webHandlers)
|
||||
// Initialize web rpc endpoint.
|
||||
apiRouter := initTestWebRPCEndPoint(obj)
|
||||
|
||||
// initialize the server and obtain the credentials and root.
|
||||
// credentials are necessary to sign the HTTP request.
|
||||
rootPath, e := newTestConfig("us-east-1")
|
||||
if e != nil {
|
||||
t.Fatalf("Init Test config failed")
|
||||
rootPath, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatal("Init Test config failed", err)
|
||||
}
|
||||
// remove the root folder after the test ends.
|
||||
defer removeAll(rootPath)
|
||||
@ -1216,9 +1207,9 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
credentials := serverConfig.GetCredential()
|
||||
authorization, e := getWebRPCToken(apiRouter, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if e != nil {
|
||||
t.Fatal("Cannot authenticate")
|
||||
authorization, err := getWebRPCToken(apiRouter, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot authenticate", err)
|
||||
}
|
||||
|
||||
// Check if web rpc calls return errors with faulty disks. ServerInfo, GenerateAuth, SetAuth, GetAuth are not concerned
|
||||
@ -1228,9 +1219,9 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) {
|
||||
for _, rpcCall := range webRPCs {
|
||||
args := &GenericArgs{}
|
||||
reply := &WebGenericRep{}
|
||||
req, err := newTestWebRPCRequest("Web."+rpcCall, authorization, args)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request: <ERROR> %v", rpcCall, err)
|
||||
req, nerr := newTestWebRPCRequest("Web."+rpcCall, authorization, args)
|
||||
if nerr != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request: <ERROR> %v", rpcCall, nerr)
|
||||
}
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
|
@ -58,7 +58,12 @@ func assetFS() *assetfs.AssetFS {
|
||||
const specialAssets = "loader.css|logo.svg|firefox.png|safari.png|chrome.png|favicon.ico"
|
||||
|
||||
// registerWebRouter - registers web router for serving minio browser.
|
||||
func registerWebRouter(mux *router.Router, web *webAPIHandlers) {
|
||||
func registerWebRouter(mux *router.Router) {
|
||||
// Initialize Web.
|
||||
web := &webAPIHandlers{
|
||||
ObjectAPI: newObjectLayerFn,
|
||||
}
|
||||
|
||||
// Initialize a new json2 codec.
|
||||
codec := json2.NewCodec()
|
||||
|
||||
|
@ -428,16 +428,6 @@ func (xl xlObjects) PutObjectPart(bucket, object, uploadID string, partID int, s
|
||||
size = sizeWritten
|
||||
}
|
||||
|
||||
// Validate if payload is valid.
|
||||
if isSignVerify(data) {
|
||||
if err = data.(*signVerifyReader).Verify(); err != nil {
|
||||
// Incoming payload wrong, delete the temporary object.
|
||||
xl.deleteObject(minioMetaBucket, tmpPartPath)
|
||||
// Returns md5 mismatch.
|
||||
return "", toObjectErr(err, bucket, object)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate new md5sum.
|
||||
newMD5Hex := hex.EncodeToString(md5Writer.Sum(nil))
|
||||
if md5Hex != "" {
|
||||
|
@ -609,16 +609,6 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io.
|
||||
}
|
||||
}
|
||||
|
||||
// Validate if payload is valid.
|
||||
if isSignVerify(data) {
|
||||
if vErr := data.(*signVerifyReader).Verify(); vErr != nil {
|
||||
// Incoming payload wrong, delete the temporary object.
|
||||
xl.deleteObject(minioMetaTmpBucket, tempObj)
|
||||
// Error return.
|
||||
return ObjectInfo{}, toObjectErr(traceError(vErr), bucket, object)
|
||||
}
|
||||
}
|
||||
|
||||
// md5Hex representation.
|
||||
md5Hex := metadata["md5Sum"]
|
||||
if md5Hex != "" {
|
||||
|
68
cmd/xl-v1.go
68
cmd/xl-v1.go
@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
"github.com/minio/minio/pkg/disk"
|
||||
"github.com/minio/minio/pkg/objcache"
|
||||
)
|
||||
@ -107,37 +106,10 @@ func repairDiskMetadata(storageDisks []StorageAPI) error {
|
||||
}
|
||||
|
||||
// newXLObjects - initialize new xl object layer.
|
||||
func newXLObjects(disks, ignoredDisks []string) (ObjectLayer, error) {
|
||||
if disks == nil {
|
||||
func newXLObjects(storageDisks []StorageAPI) (ObjectLayer, error) {
|
||||
if storageDisks == nil {
|
||||
return nil, errInvalidArgument
|
||||
}
|
||||
disksSet := set.NewStringSet()
|
||||
if len(ignoredDisks) > 0 {
|
||||
disksSet = set.CreateStringSet(ignoredDisks...)
|
||||
}
|
||||
// Bootstrap disks.
|
||||
storageDisks := make([]StorageAPI, len(disks))
|
||||
for index, disk := range disks {
|
||||
// Check if disk is ignored.
|
||||
if disksSet.Contains(disk) {
|
||||
storageDisks[index] = nil
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
// Intentionally ignore disk not found errors. XL is designed
|
||||
// to handle these errors internally.
|
||||
storageDisks[index], err = newStorageAPI(disk)
|
||||
if err != nil && err != errDiskNotFound {
|
||||
switch diskType := storageDisks[index].(type) {
|
||||
case networkStorage:
|
||||
diskType.rpcClient.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Fix format files in case of fresh or corrupted disks
|
||||
repairDiskMetadata(storageDisks)
|
||||
|
||||
// Runs house keeping code, like t, cleaning up tmp files etc.
|
||||
if err := xlHouseKeeping(storageDisks); err != nil {
|
||||
@ -147,7 +119,6 @@ func newXLObjects(disks, ignoredDisks []string) (ObjectLayer, error) {
|
||||
// Load saved XL format.json and validate.
|
||||
newPosixDisks, err := loadFormatXL(storageDisks)
|
||||
if err != nil {
|
||||
// errCorruptedDisk - healing failed
|
||||
return nil, fmt.Errorf("Unable to recognize backend format, %s", err)
|
||||
}
|
||||
|
||||
@ -204,25 +175,36 @@ func (d byDiskTotal) Less(i, j int) bool {
|
||||
return d[i].Total < d[j].Total
|
||||
}
|
||||
|
||||
// StorageInfo - returns underlying storage statistics.
|
||||
func (xl xlObjects) StorageInfo() StorageInfo {
|
||||
var disksInfo []disk.Info
|
||||
for _, storageDisk := range xl.storageDisks {
|
||||
// getDisksInfo - fetch disks info across all other storage API.
|
||||
func getDisksInfo(disks []StorageAPI) (disksInfo []disk.Info, onlineDisks int, offlineDisks int) {
|
||||
for _, storageDisk := range disks {
|
||||
if storageDisk == nil {
|
||||
// Storage disk is empty, perhaps ignored disk or not available.
|
||||
offlineDisks++
|
||||
continue
|
||||
}
|
||||
info, err := storageDisk.DiskInfo()
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to fetch disk info for %#v", storageDisk)
|
||||
if err == errDiskNotFound {
|
||||
offlineDisks++
|
||||
}
|
||||
continue
|
||||
}
|
||||
onlineDisks++
|
||||
disksInfo = append(disksInfo, info)
|
||||
}
|
||||
|
||||
// Sort so that the first element is the smallest.
|
||||
sort.Sort(byDiskTotal(disksInfo))
|
||||
|
||||
// Success.
|
||||
return disksInfo, onlineDisks, offlineDisks
|
||||
}
|
||||
|
||||
// Get an aggregated storage info across all disks.
|
||||
func getStorageInfo(disks []StorageAPI) StorageInfo {
|
||||
disksInfo, onlineDisks, offlineDisks := getDisksInfo(disks)
|
||||
if len(disksInfo) == 0 {
|
||||
return StorageInfo{
|
||||
Total: -1,
|
||||
@ -232,8 +214,18 @@ func (xl xlObjects) StorageInfo() StorageInfo {
|
||||
// Return calculated storage info, choose the lowest Total and
|
||||
// Free as the total aggregated values. Total capacity is always
|
||||
// the multiple of smallest disk among the disk list.
|
||||
return StorageInfo{
|
||||
Total: disksInfo[0].Total * int64(len(xl.storageDisks)),
|
||||
Free: disksInfo[0].Free * int64(len(xl.storageDisks)),
|
||||
storageInfo := StorageInfo{
|
||||
Total: disksInfo[0].Total * int64(onlineDisks),
|
||||
Free: disksInfo[0].Free * int64(onlineDisks),
|
||||
}
|
||||
storageInfo.Backend.Type = XL
|
||||
storageInfo.Backend.OnlineDisks = onlineDisks
|
||||
storageInfo.Backend.OfflineDisks = offlineDisks
|
||||
storageInfo.Backend.Quorum = len(disks) / 2
|
||||
return storageInfo
|
||||
}
|
||||
|
||||
// StorageInfo - returns underlying storage statistics.
|
||||
func (xl xlObjects) StorageInfo() StorageInfo {
|
||||
return getStorageInfo(xl.storageDisks)
|
||||
}
|
||||
|
@ -48,7 +48,12 @@ func TestStorageInfo(t *testing.T) {
|
||||
t.Fatalf("Diskinfo total values should be greater 0")
|
||||
}
|
||||
|
||||
objLayer, err = newXLObjects(fsDirs, fsDirs[:4])
|
||||
storageDisks, err := initStorageDisks(fsDirs, fsDirs[:4])
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
||||
objLayer, err = newXLObjects(storageDisks)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize 'XL' object layer with ignored disks %s.", fsDirs[:4])
|
||||
}
|
||||
@ -83,23 +88,38 @@ func TestNewXL(t *testing.T) {
|
||||
}
|
||||
|
||||
// No disks input.
|
||||
_, err := newXLObjects(nil, nil)
|
||||
_, err := newXLObjects(nil)
|
||||
if err != errInvalidArgument {
|
||||
t.Fatalf("Unable to initialize erasure, %s", err)
|
||||
}
|
||||
|
||||
storageDisks, err := initStorageDisks(erasureDisks, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
||||
err = waitForFormatDisks(true, "", nil)
|
||||
if err != errInvalidArgument {
|
||||
t.Fatalf("Expecting error, got %s", err)
|
||||
}
|
||||
|
||||
// Initializes all erasure disks
|
||||
err = formatDisks(erasureDisks, nil)
|
||||
err = waitForFormatDisks(true, "", storageDisks)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to format disks for erasure, %s", err)
|
||||
}
|
||||
_, err = newXLObjects(erasureDisks, nil)
|
||||
_, err = newXLObjects(storageDisks)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize erasure, %s", err)
|
||||
}
|
||||
|
||||
storageDisks, err = initStorageDisks(erasureDisks, erasureDisks[:2])
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
||||
// Initializes all erasure disks, ignoring first two.
|
||||
_, err = newXLObjects(erasureDisks, erasureDisks[:2])
|
||||
_, err = newXLObjects(storageDisks)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize erasure, %s", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user