mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 02:01:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			989 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			989 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc.
 | 
						|
 *
 | 
						|
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
 * you may not use this file except in compliance with the License.
 | 
						|
 * You may obtain a copy of the License at
 | 
						|
 *
 | 
						|
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 *
 | 
						|
 * Unless required by applicable law or agreed to in writing, software
 | 
						|
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
 * See the License for the specific language governing permissions and
 | 
						|
 * limitations under the License.
 | 
						|
 */
 | 
						|
 | 
						|
package cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/json"
 | 
						|
	"encoding/xml"
 | 
						|
	"io/ioutil"
 | 
						|
	"net/http"
 | 
						|
	"net/http/httptest"
 | 
						|
	"net/url"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	router "github.com/gorilla/mux"
 | 
						|
)
 | 
						|
 | 
						|
// adminXLTestBed - encapsulates subsystems that need to be setup for
 | 
						|
// admin-handler unit tests.
 | 
						|
type adminXLTestBed struct {
 | 
						|
	configPath string
 | 
						|
	xlDirs     []string
 | 
						|
	objLayer   ObjectLayer
 | 
						|
	mux        *router.Router
 | 
						|
}
 | 
						|
 | 
						|
// prepareAdminXLTestBed - helper function that setups a single-node
 | 
						|
// XL backend for admin-handler tests.
 | 
						|
func prepareAdminXLTestBed() (*adminXLTestBed, error) {
 | 
						|
	// reset global variables to start afresh.
 | 
						|
	resetTestGlobals()
 | 
						|
	// Initialize minio server config.
 | 
						|
	rootPath, err := newTestConfig(globalMinioDefaultRegion)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// Initializing objectLayer for HealFormatHandler.
 | 
						|
	objLayer, xlDirs, xlErr := initTestXLObjLayer()
 | 
						|
	if xlErr != nil {
 | 
						|
		return nil, xlErr
 | 
						|
	}
 | 
						|
 | 
						|
	// Set globalEndpoints for a single node XL setup.
 | 
						|
	for _, xlDir := range xlDirs {
 | 
						|
		globalEndpoints = append(globalEndpoints, &url.URL{
 | 
						|
			Path: xlDir,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	// Set globalIsXL to indicate that the setup uses an erasure code backend.
 | 
						|
	globalIsXL = true
 | 
						|
 | 
						|
	// initialize NSLock.
 | 
						|
	isDistXL := false
 | 
						|
	initNSLock(isDistXL)
 | 
						|
 | 
						|
	// Setup admin mgmt REST API handlers.
 | 
						|
	adminRouter := router.NewRouter()
 | 
						|
	registerAdminRouter(adminRouter)
 | 
						|
 | 
						|
	return &adminXLTestBed{
 | 
						|
		configPath: rootPath,
 | 
						|
		xlDirs:     xlDirs,
 | 
						|
		objLayer:   objLayer,
 | 
						|
		mux:        adminRouter,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// TearDown - method that resets the test bed for subsequent unit
 | 
						|
// tests to start afresh.
 | 
						|
func (atb *adminXLTestBed) TearDown() {
 | 
						|
	removeAll(atb.configPath)
 | 
						|
	removeRoots(atb.xlDirs)
 | 
						|
	resetTestGlobals()
 | 
						|
}
 | 
						|
 | 
						|
// initTestObjLayer - Helper function to initialize an XL-based object
 | 
						|
// layer and set globalObjectAPI.
 | 
						|
func initTestXLObjLayer() (ObjectLayer, []string, error) {
 | 
						|
	objLayer, xlDirs, xlErr := prepareXL()
 | 
						|
	if xlErr != nil {
 | 
						|
		return nil, nil, xlErr
 | 
						|
	}
 | 
						|
	// Make objLayer available to all internal services via globalObjectAPI.
 | 
						|
	globalObjLayerMutex.Lock()
 | 
						|
	globalObjectAPI = objLayer
 | 
						|
	globalObjLayerMutex.Unlock()
 | 
						|
	return objLayer, xlDirs, nil
 | 
						|
}
 | 
						|
 | 
						|
// cmdType - Represents different service subcomands like status, stop
 | 
						|
// and restart.
 | 
						|
type cmdType int
 | 
						|
 | 
						|
const (
 | 
						|
	statusCmd cmdType = iota
 | 
						|
	restartCmd
 | 
						|
	setCreds
 | 
						|
)
 | 
						|
 | 
						|
// String - String representation for cmdType
 | 
						|
func (c cmdType) String() string {
 | 
						|
	switch c {
 | 
						|
	case statusCmd:
 | 
						|
		return "status"
 | 
						|
	case restartCmd:
 | 
						|
		return "restart"
 | 
						|
	case setCreds:
 | 
						|
		return "set-credentials"
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
// apiMethod - Returns the HTTP method corresponding to the admin REST
 | 
						|
// API for a given cmdType value.
 | 
						|
func (c cmdType) apiMethod() string {
 | 
						|
	switch c {
 | 
						|
	case statusCmd:
 | 
						|
		return "GET"
 | 
						|
	case restartCmd:
 | 
						|
		return "POST"
 | 
						|
	case setCreds:
 | 
						|
		return "POST"
 | 
						|
	}
 | 
						|
	return "GET"
 | 
						|
}
 | 
						|
 | 
						|
// toServiceSignal - Helper function that translates a given cmdType
 | 
						|
// value to its corresponding serviceSignal value.
 | 
						|
func (c cmdType) toServiceSignal() serviceSignal {
 | 
						|
	switch c {
 | 
						|
	case statusCmd:
 | 
						|
		return serviceStatus
 | 
						|
	case restartCmd:
 | 
						|
		return serviceRestart
 | 
						|
	}
 | 
						|
	return serviceStatus
 | 
						|
}
 | 
						|
 | 
						|
// testServiceSignalReceiver - Helper function that simulates a
 | 
						|
// go-routine waiting on service signal.
 | 
						|
func testServiceSignalReceiver(cmd cmdType, t *testing.T) {
 | 
						|
	expectedCmd := cmd.toServiceSignal()
 | 
						|
	serviceCmd := <-globalServiceSignalCh
 | 
						|
	if serviceCmd != expectedCmd {
 | 
						|
		t.Errorf("Expected service command %v but received %v", expectedCmd, serviceCmd)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// getServiceCmdRequest - Constructs a management REST API request for service
 | 
						|
// subcommands for a given cmdType value.
 | 
						|
func getServiceCmdRequest(cmd cmdType, cred credential, body []byte) (*http.Request, error) {
 | 
						|
	req, err := newTestRequest(cmd.apiMethod(), "/?service", 0, nil)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Set body
 | 
						|
	req.Body = ioutil.NopCloser(bytes.NewReader(body))
 | 
						|
 | 
						|
	// minioAdminOpHeader is to identify the request as a
 | 
						|
	// management REST API request.
 | 
						|
	req.Header.Set(minioAdminOpHeader, cmd.String())
 | 
						|
	req.Header.Set("X-Amz-Content-Sha256", getSHA256Hash(body))
 | 
						|
 | 
						|
	// management REST API uses signature V4 for authentication.
 | 
						|
	err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return req, nil
 | 
						|
}
 | 
						|
 | 
						|
// testServicesCmdHandler - parametrizes service subcommand tests on
 | 
						|
// cmdType value.
 | 
						|
func testServicesCmdHandler(cmd cmdType, t *testing.T) {
 | 
						|
	adminTestBed, err := prepareAdminXLTestBed()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
 | 
						|
	}
 | 
						|
	defer adminTestBed.TearDown()
 | 
						|
 | 
						|
	// Initialize admin peers to make admin RPC calls. Note: In a
 | 
						|
	// single node setup, this degenerates to a simple function
 | 
						|
	// call under the hood.
 | 
						|
	eps, err := parseStorageEndpoints([]string{"http://localhost"})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to parse storage end point - %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Set globalMinioAddr to be able to distinguish local endpoints from remote.
 | 
						|
	globalMinioAddr = eps[0].Host
 | 
						|
	initGlobalAdminPeers(eps)
 | 
						|
 | 
						|
	// Setting up a go routine to simulate ServerMux's
 | 
						|
	// handleServiceSignals for stop and restart commands.
 | 
						|
	if cmd == restartCmd {
 | 
						|
		go testServiceSignalReceiver(cmd, t)
 | 
						|
	}
 | 
						|
	credentials := serverConfig.GetCredential()
 | 
						|
	var body []byte
 | 
						|
 | 
						|
	req, err := getServiceCmdRequest(cmd, credentials, body)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to build service status request %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	rec := httptest.NewRecorder()
 | 
						|
	adminTestBed.mux.ServeHTTP(rec, req)
 | 
						|
 | 
						|
	if cmd == statusCmd {
 | 
						|
		expectedInfo := ServerStatus{
 | 
						|
			StorageInfo:   newObjectLayerFn().StorageInfo(),
 | 
						|
			ServerVersion: ServerVersion{Version: Version, CommitID: CommitID},
 | 
						|
		}
 | 
						|
		receivedInfo := ServerStatus{}
 | 
						|
		if jsonErr := json.Unmarshal(rec.Body.Bytes(), &receivedInfo); jsonErr != nil {
 | 
						|
			t.Errorf("Failed to unmarshal StorageInfo - %v", jsonErr)
 | 
						|
		}
 | 
						|
		if expectedInfo != receivedInfo {
 | 
						|
			t.Errorf("Expected storage info and received storage info differ, %v %v", expectedInfo, receivedInfo)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if rec.Code != http.StatusOK {
 | 
						|
		resp, _ := ioutil.ReadAll(rec.Body)
 | 
						|
		t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
 | 
						|
			http.StatusOK, rec.Code, string(resp))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Test for service status management REST API.
 | 
						|
func TestServiceStatusHandler(t *testing.T) {
 | 
						|
	testServicesCmdHandler(statusCmd, t)
 | 
						|
}
 | 
						|
 | 
						|
// Test for service restart management REST API.
 | 
						|
func TestServiceRestartHandler(t *testing.T) {
 | 
						|
	testServicesCmdHandler(restartCmd, t)
 | 
						|
}
 | 
						|
 | 
						|
// Test for service set creds management REST API.
 | 
						|
func TestServiceSetCreds(t *testing.T) {
 | 
						|
	adminTestBed, err := prepareAdminXLTestBed()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
 | 
						|
	}
 | 
						|
	defer adminTestBed.TearDown()
 | 
						|
 | 
						|
	// Initialize admin peers to make admin RPC calls. Note: In a
 | 
						|
	// single node setup, this degenerates to a simple function
 | 
						|
	// call under the hood.
 | 
						|
	eps, err := parseStorageEndpoints([]string{"http://localhost"})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to parse storage end point - %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Set globalMinioAddr to be able to distinguish local endpoints from remote.
 | 
						|
	globalMinioAddr = eps[0].Host
 | 
						|
	initGlobalAdminPeers(eps)
 | 
						|
 | 
						|
	credentials := serverConfig.GetCredential()
 | 
						|
	var body []byte
 | 
						|
 | 
						|
	testCases := []struct {
 | 
						|
		Username           string
 | 
						|
		Password           string
 | 
						|
		EnvKeysSet         bool
 | 
						|
		ExpectedStatusCode int
 | 
						|
	}{
 | 
						|
		// Bad secret key
 | 
						|
		{"minio", "minio", false, http.StatusBadRequest},
 | 
						|
		// Bad  secret key set from the env
 | 
						|
		{"minio", "minio", true, http.StatusMethodNotAllowed},
 | 
						|
		// Good keys set from the env
 | 
						|
		{"minio", "minio123", true, http.StatusMethodNotAllowed},
 | 
						|
		// Successful operation should be the last one to do not change server credentials during tests.
 | 
						|
		{"minio", "minio123", false, http.StatusOK},
 | 
						|
	}
 | 
						|
	for i, testCase := range testCases {
 | 
						|
		// Set or unset environement keys
 | 
						|
		if !testCase.EnvKeysSet {
 | 
						|
			globalEnvAccessKey = ""
 | 
						|
			globalEnvSecretKey = ""
 | 
						|
		} else {
 | 
						|
			globalEnvAccessKey = testCase.Username
 | 
						|
			globalEnvSecretKey = testCase.Password
 | 
						|
		}
 | 
						|
 | 
						|
		// Construct setCreds request body
 | 
						|
		body, _ = xml.Marshal(setCredsReq{Username: testCase.Username, Password: testCase.Password})
 | 
						|
		// Construct setCreds request
 | 
						|
		req, err := getServiceCmdRequest(setCreds, credentials, body)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Failed to build service status request %v", err)
 | 
						|
		}
 | 
						|
 | 
						|
		rec := httptest.NewRecorder()
 | 
						|
 | 
						|
		// Execute request
 | 
						|
		adminTestBed.mux.ServeHTTP(rec, req)
 | 
						|
 | 
						|
		// Check if the http code response is expected
 | 
						|
		if rec.Code != testCase.ExpectedStatusCode {
 | 
						|
			t.Errorf("Test %d: Wrong status code, expected = %d, found = %d", i+1, testCase.ExpectedStatusCode, rec.Code)
 | 
						|
			resp, _ := ioutil.ReadAll(rec.Body)
 | 
						|
			t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
 | 
						|
				http.StatusOK, rec.Code, string(resp))
 | 
						|
		}
 | 
						|
 | 
						|
		// If we got 200 OK, check if new credentials are really set
 | 
						|
		if rec.Code == http.StatusOK {
 | 
						|
			cred := serverConfig.GetCredential()
 | 
						|
			if cred.AccessKey != testCase.Username {
 | 
						|
				t.Errorf("Test %d: Wrong access key, expected = %s, found = %s", i+1, testCase.Username, cred.AccessKey)
 | 
						|
			}
 | 
						|
			if cred.SecretKey != testCase.Password {
 | 
						|
				t.Errorf("Test %d: Wrong secret key, expected = %s, found = %s", i+1, testCase.Password, cred.SecretKey)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// mkLockQueryVal - helper function to build lock query param.
 | 
						|
func mkLockQueryVal(bucket, prefix, relTimeStr string) url.Values {
 | 
						|
	qVal := url.Values{}
 | 
						|
	qVal.Set("lock", "")
 | 
						|
	qVal.Set(string(mgmtBucket), bucket)
 | 
						|
	qVal.Set(string(mgmtPrefix), prefix)
 | 
						|
	qVal.Set(string(mgmtOlderThan), relTimeStr)
 | 
						|
	return qVal
 | 
						|
}
 | 
						|
 | 
						|
// Test for locks list management REST API.
 | 
						|
func TestListLocksHandler(t *testing.T) {
 | 
						|
	adminTestBed, err := prepareAdminXLTestBed()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
 | 
						|
	}
 | 
						|
	defer adminTestBed.TearDown()
 | 
						|
 | 
						|
	// Initialize admin peers to make admin RPC calls.
 | 
						|
	eps, err := parseStorageEndpoints([]string{"http://localhost"})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to parse storage end point - %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Set globalMinioAddr to be able to distinguish local endpoints from remote.
 | 
						|
	globalMinioAddr = eps[0].Host
 | 
						|
	initGlobalAdminPeers(eps)
 | 
						|
 | 
						|
	testCases := []struct {
 | 
						|
		bucket         string
 | 
						|
		prefix         string
 | 
						|
		relTime        string
 | 
						|
		expectedStatus int
 | 
						|
	}{
 | 
						|
		// Test 1 - valid testcase
 | 
						|
		{
 | 
						|
			bucket:         "mybucket",
 | 
						|
			prefix:         "myobject",
 | 
						|
			relTime:        "1s",
 | 
						|
			expectedStatus: http.StatusOK,
 | 
						|
		},
 | 
						|
		// Test 2 - invalid duration
 | 
						|
		{
 | 
						|
			bucket:         "mybucket",
 | 
						|
			prefix:         "myprefix",
 | 
						|
			relTime:        "invalidDuration",
 | 
						|
			expectedStatus: http.StatusBadRequest,
 | 
						|
		},
 | 
						|
		// Test 3 - invalid bucket name
 | 
						|
		{
 | 
						|
			bucket:         `invalid\\Bucket`,
 | 
						|
			prefix:         "myprefix",
 | 
						|
			relTime:        "1h",
 | 
						|
			expectedStatus: http.StatusBadRequest,
 | 
						|
		},
 | 
						|
		// Test 4 - invalid prefix
 | 
						|
		{
 | 
						|
			bucket:         "mybucket",
 | 
						|
			prefix:         `invalid\\Prefix`,
 | 
						|
			relTime:        "1h",
 | 
						|
			expectedStatus: http.StatusBadRequest,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, test := range testCases {
 | 
						|
		queryVal := mkLockQueryVal(test.bucket, test.prefix, test.relTime)
 | 
						|
		req, err := newTestRequest("GET", "/?"+queryVal.Encode(), 0, nil)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to construct list locks request - %v", i+1, err)
 | 
						|
		}
 | 
						|
		req.Header.Set(minioAdminOpHeader, "list")
 | 
						|
 | 
						|
		cred := serverConfig.GetCredential()
 | 
						|
		err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to sign list locks request - %v", i+1, err)
 | 
						|
		}
 | 
						|
		rec := httptest.NewRecorder()
 | 
						|
		adminTestBed.mux.ServeHTTP(rec, req)
 | 
						|
		if test.expectedStatus != rec.Code {
 | 
						|
			t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.expectedStatus, rec.Code)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Test for locks clear management REST API.
 | 
						|
func TestClearLocksHandler(t *testing.T) {
 | 
						|
	adminTestBed, err := prepareAdminXLTestBed()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
 | 
						|
	}
 | 
						|
	defer adminTestBed.TearDown()
 | 
						|
 | 
						|
	// Initialize admin peers to make admin RPC calls.
 | 
						|
	eps, err := parseStorageEndpoints([]string{"http://localhost"})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to parse storage end point - %v", err)
 | 
						|
	}
 | 
						|
	initGlobalAdminPeers(eps)
 | 
						|
 | 
						|
	testCases := []struct {
 | 
						|
		bucket         string
 | 
						|
		prefix         string
 | 
						|
		relTime        string
 | 
						|
		expectedStatus int
 | 
						|
	}{
 | 
						|
		// Test 1 - valid testcase
 | 
						|
		{
 | 
						|
			bucket:         "mybucket",
 | 
						|
			prefix:         "myobject",
 | 
						|
			relTime:        "1s",
 | 
						|
			expectedStatus: http.StatusOK,
 | 
						|
		},
 | 
						|
		// Test 2 - invalid duration
 | 
						|
		{
 | 
						|
			bucket:         "mybucket",
 | 
						|
			prefix:         "myprefix",
 | 
						|
			relTime:        "invalidDuration",
 | 
						|
			expectedStatus: http.StatusBadRequest,
 | 
						|
		},
 | 
						|
		// Test 3 - invalid bucket name
 | 
						|
		{
 | 
						|
			bucket:         `invalid\\Bucket`,
 | 
						|
			prefix:         "myprefix",
 | 
						|
			relTime:        "1h",
 | 
						|
			expectedStatus: http.StatusBadRequest,
 | 
						|
		},
 | 
						|
		// Test 4 - invalid prefix
 | 
						|
		{
 | 
						|
			bucket:         "mybucket",
 | 
						|
			prefix:         `invalid\\Prefix`,
 | 
						|
			relTime:        "1h",
 | 
						|
			expectedStatus: http.StatusBadRequest,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, test := range testCases {
 | 
						|
		queryVal := mkLockQueryVal(test.bucket, test.prefix, test.relTime)
 | 
						|
		req, err := newTestRequest("POST", "/?"+queryVal.Encode(), 0, nil)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to construct clear locks request - %v", i+1, err)
 | 
						|
		}
 | 
						|
		req.Header.Set(minioAdminOpHeader, "clear")
 | 
						|
 | 
						|
		cred := serverConfig.GetCredential()
 | 
						|
		err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to sign clear locks request - %v", i+1, err)
 | 
						|
		}
 | 
						|
		rec := httptest.NewRecorder()
 | 
						|
		adminTestBed.mux.ServeHTTP(rec, req)
 | 
						|
		if test.expectedStatus != rec.Code {
 | 
						|
			t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.expectedStatus, rec.Code)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Test for lock query param validation helper function.
 | 
						|
func TestValidateLockQueryParams(t *testing.T) {
 | 
						|
	// reset globals.
 | 
						|
	// this is to make sure that the tests are not affected by modified globals.
 | 
						|
	resetTestGlobals()
 | 
						|
	// initialize NSLock.
 | 
						|
	initNSLock(false)
 | 
						|
	// Sample query values for test cases.
 | 
						|
	allValidVal := mkLockQueryVal("bucket", "prefix", "1s")
 | 
						|
	invalidBucketVal := mkLockQueryVal(`invalid\\Bucket`, "prefix", "1s")
 | 
						|
	invalidPrefixVal := mkLockQueryVal("bucket", `invalid\\Prefix`, "1s")
 | 
						|
	invalidOlderThanVal := mkLockQueryVal("bucket", "prefix", "invalidDuration")
 | 
						|
 | 
						|
	testCases := []struct {
 | 
						|
		qVals  url.Values
 | 
						|
		apiErr APIErrorCode
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			qVals:  invalidBucketVal,
 | 
						|
			apiErr: ErrInvalidBucketName,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			qVals:  invalidPrefixVal,
 | 
						|
			apiErr: ErrInvalidObjectName,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			qVals:  invalidOlderThanVal,
 | 
						|
			apiErr: ErrInvalidDuration,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			qVals:  allValidVal,
 | 
						|
			apiErr: ErrNone,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, test := range testCases {
 | 
						|
		_, _, _, apiErr := validateLockQueryParams(test.qVals)
 | 
						|
		if apiErr != test.apiErr {
 | 
						|
			t.Errorf("Test %d - Expected error %v but received %v", i+1, test.apiErr, apiErr)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// mkListObjectsQueryStr - helper to build ListObjectsHeal query string.
 | 
						|
func mkListObjectsQueryVal(bucket, prefix, marker, delimiter, maxKeyStr string) url.Values {
 | 
						|
	qVal := url.Values{}
 | 
						|
	qVal.Set("heal", "")
 | 
						|
	qVal.Set(string(mgmtBucket), bucket)
 | 
						|
	qVal.Set(string(mgmtPrefix), prefix)
 | 
						|
	qVal.Set(string(mgmtMarker), marker)
 | 
						|
	qVal.Set(string(mgmtDelimiter), delimiter)
 | 
						|
	qVal.Set(string(mgmtMaxKey), maxKeyStr)
 | 
						|
	return qVal
 | 
						|
}
 | 
						|
 | 
						|
// TestValidateHealQueryParams - Test for query param validation helper function for heal APIs.
 | 
						|
func TestValidateHealQueryParams(t *testing.T) {
 | 
						|
	testCases := []struct {
 | 
						|
		bucket    string
 | 
						|
		prefix    string
 | 
						|
		marker    string
 | 
						|
		delimiter string
 | 
						|
		maxKeys   string
 | 
						|
		apiErr    APIErrorCode
 | 
						|
	}{
 | 
						|
		// 1. Valid params.
 | 
						|
		{
 | 
						|
			bucket:    "mybucket",
 | 
						|
			prefix:    "prefix",
 | 
						|
			marker:    "prefix11",
 | 
						|
			delimiter: "/",
 | 
						|
			maxKeys:   "10",
 | 
						|
			apiErr:    ErrNone,
 | 
						|
		},
 | 
						|
		// 2. Valid params with meta bucket.
 | 
						|
		{
 | 
						|
			bucket:    minioMetaBucket,
 | 
						|
			prefix:    "prefix",
 | 
						|
			marker:    "prefix11",
 | 
						|
			delimiter: "/",
 | 
						|
			maxKeys:   "10",
 | 
						|
			apiErr:    ErrNone,
 | 
						|
		},
 | 
						|
		// 3. Valid params with empty prefix.
 | 
						|
		{
 | 
						|
			bucket:    "mybucket",
 | 
						|
			prefix:    "",
 | 
						|
			marker:    "",
 | 
						|
			delimiter: "/",
 | 
						|
			maxKeys:   "10",
 | 
						|
			apiErr:    ErrNone,
 | 
						|
		},
 | 
						|
		// 4. Invalid params with invalid bucket.
 | 
						|
		{
 | 
						|
			bucket:    `invalid\\Bucket`,
 | 
						|
			prefix:    "prefix",
 | 
						|
			marker:    "prefix11",
 | 
						|
			delimiter: "/",
 | 
						|
			maxKeys:   "10",
 | 
						|
			apiErr:    ErrInvalidBucketName,
 | 
						|
		},
 | 
						|
		// 5. Invalid params with invalid prefix.
 | 
						|
		{
 | 
						|
			bucket:    "mybucket",
 | 
						|
			prefix:    `invalid\\Prefix`,
 | 
						|
			marker:    "prefix11",
 | 
						|
			delimiter: "/",
 | 
						|
			maxKeys:   "10",
 | 
						|
			apiErr:    ErrInvalidObjectName,
 | 
						|
		},
 | 
						|
		// 6. Invalid params with invalid maxKeys.
 | 
						|
		{
 | 
						|
			bucket:    "mybucket",
 | 
						|
			prefix:    "prefix",
 | 
						|
			marker:    "prefix11",
 | 
						|
			delimiter: "/",
 | 
						|
			maxKeys:   "-1",
 | 
						|
			apiErr:    ErrInvalidMaxKeys,
 | 
						|
		},
 | 
						|
		// 7. Invalid params with unsupported prefix marker combination.
 | 
						|
		{
 | 
						|
			bucket:    "mybucket",
 | 
						|
			prefix:    "prefix",
 | 
						|
			marker:    "notmatchingmarker",
 | 
						|
			delimiter: "/",
 | 
						|
			maxKeys:   "10",
 | 
						|
			apiErr:    ErrNotImplemented,
 | 
						|
		},
 | 
						|
		// 8. Invalid params with unsupported delimiter.
 | 
						|
		{
 | 
						|
			bucket:    "mybucket",
 | 
						|
			prefix:    "prefix",
 | 
						|
			marker:    "notmatchingmarker",
 | 
						|
			delimiter: "unsupported",
 | 
						|
			maxKeys:   "10",
 | 
						|
			apiErr:    ErrNotImplemented,
 | 
						|
		},
 | 
						|
		// 9. Invalid params with invalid max Keys
 | 
						|
		{
 | 
						|
			bucket:    "mybucket",
 | 
						|
			prefix:    "prefix",
 | 
						|
			marker:    "prefix11",
 | 
						|
			delimiter: "/",
 | 
						|
			maxKeys:   "999999999999999999999999999",
 | 
						|
			apiErr:    ErrInvalidMaxKeys,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for i, test := range testCases {
 | 
						|
		vars := mkListObjectsQueryVal(test.bucket, test.prefix, test.marker, test.delimiter, test.maxKeys)
 | 
						|
		_, _, _, _, _, actualErr := validateHealQueryParams(vars)
 | 
						|
		if actualErr != test.apiErr {
 | 
						|
			t.Errorf("Test %d - Expected %v but received %v",
 | 
						|
				i+1, getAPIError(test.apiErr), getAPIError(actualErr))
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestListObjectsHeal - Test for ListObjectsHealHandler.
 | 
						|
func TestListObjectsHealHandler(t *testing.T) {
 | 
						|
	adminTestBed, err := prepareAdminXLTestBed()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
 | 
						|
	}
 | 
						|
	defer adminTestBed.TearDown()
 | 
						|
 | 
						|
	err = adminTestBed.objLayer.MakeBucket("mybucket")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to make bucket - %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Delete bucket after running all test cases.
 | 
						|
	defer adminTestBed.objLayer.DeleteBucket("mybucket")
 | 
						|
 | 
						|
	testCases := []struct {
 | 
						|
		bucket     string
 | 
						|
		prefix     string
 | 
						|
		marker     string
 | 
						|
		delimiter  string
 | 
						|
		maxKeys    string
 | 
						|
		statusCode int
 | 
						|
	}{
 | 
						|
		// 1. Valid params.
 | 
						|
		{
 | 
						|
			bucket:     "mybucket",
 | 
						|
			prefix:     "prefix",
 | 
						|
			marker:     "prefix11",
 | 
						|
			delimiter:  "/",
 | 
						|
			maxKeys:    "10",
 | 
						|
			statusCode: http.StatusOK,
 | 
						|
		},
 | 
						|
		// 2. Valid params with meta bucket.
 | 
						|
		{
 | 
						|
			bucket:     minioMetaBucket,
 | 
						|
			prefix:     "prefix",
 | 
						|
			marker:     "prefix11",
 | 
						|
			delimiter:  "/",
 | 
						|
			maxKeys:    "10",
 | 
						|
			statusCode: http.StatusOK,
 | 
						|
		},
 | 
						|
		// 3. Valid params with empty prefix.
 | 
						|
		{
 | 
						|
			bucket:     "mybucket",
 | 
						|
			prefix:     "",
 | 
						|
			marker:     "",
 | 
						|
			delimiter:  "/",
 | 
						|
			maxKeys:    "10",
 | 
						|
			statusCode: http.StatusOK,
 | 
						|
		},
 | 
						|
		// 4. Invalid params with invalid bucket.
 | 
						|
		{
 | 
						|
			bucket:     `invalid\\Bucket`,
 | 
						|
			prefix:     "prefix",
 | 
						|
			marker:     "prefix11",
 | 
						|
			delimiter:  "/",
 | 
						|
			maxKeys:    "10",
 | 
						|
			statusCode: getAPIError(ErrInvalidBucketName).HTTPStatusCode,
 | 
						|
		},
 | 
						|
		// 5. Invalid params with invalid prefix.
 | 
						|
		{
 | 
						|
			bucket:     "mybucket",
 | 
						|
			prefix:     `invalid\\Prefix`,
 | 
						|
			marker:     "prefix11",
 | 
						|
			delimiter:  "/",
 | 
						|
			maxKeys:    "10",
 | 
						|
			statusCode: getAPIError(ErrInvalidObjectName).HTTPStatusCode,
 | 
						|
		},
 | 
						|
		// 6. Invalid params with invalid maxKeys.
 | 
						|
		{
 | 
						|
			bucket:     "mybucket",
 | 
						|
			prefix:     "prefix",
 | 
						|
			marker:     "prefix11",
 | 
						|
			delimiter:  "/",
 | 
						|
			maxKeys:    "-1",
 | 
						|
			statusCode: getAPIError(ErrInvalidMaxKeys).HTTPStatusCode,
 | 
						|
		},
 | 
						|
		// 7. Invalid params with unsupported prefix marker combination.
 | 
						|
		{
 | 
						|
			bucket:     "mybucket",
 | 
						|
			prefix:     "prefix",
 | 
						|
			marker:     "notmatchingmarker",
 | 
						|
			delimiter:  "/",
 | 
						|
			maxKeys:    "10",
 | 
						|
			statusCode: getAPIError(ErrNotImplemented).HTTPStatusCode,
 | 
						|
		},
 | 
						|
		// 8. Invalid params with unsupported delimiter.
 | 
						|
		{
 | 
						|
			bucket:     "mybucket",
 | 
						|
			prefix:     "prefix",
 | 
						|
			marker:     "notmatchingmarker",
 | 
						|
			delimiter:  "unsupported",
 | 
						|
			maxKeys:    "10",
 | 
						|
			statusCode: getAPIError(ErrNotImplemented).HTTPStatusCode,
 | 
						|
		},
 | 
						|
		// 9. Invalid params with invalid max Keys
 | 
						|
		{
 | 
						|
			bucket:     "mybucket",
 | 
						|
			prefix:     "prefix",
 | 
						|
			marker:     "prefix11",
 | 
						|
			delimiter:  "/",
 | 
						|
			maxKeys:    "999999999999999999999999999",
 | 
						|
			statusCode: getAPIError(ErrInvalidMaxKeys).HTTPStatusCode,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, test := range testCases {
 | 
						|
		if i != 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		queryVal := mkListObjectsQueryVal(test.bucket, test.prefix, test.marker, test.delimiter, test.maxKeys)
 | 
						|
		req, err := newTestRequest("GET", "/?"+queryVal.Encode(), 0, nil)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to construct list objects needing heal request - %v", i+1, err)
 | 
						|
		}
 | 
						|
		req.Header.Set(minioAdminOpHeader, "list-objects")
 | 
						|
 | 
						|
		cred := serverConfig.GetCredential()
 | 
						|
		err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to sign list objects needing heal request - %v", i+1, err)
 | 
						|
		}
 | 
						|
		rec := httptest.NewRecorder()
 | 
						|
		adminTestBed.mux.ServeHTTP(rec, req)
 | 
						|
		if test.statusCode != rec.Code {
 | 
						|
			t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.statusCode, rec.Code)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestHealBucketHandler - Test for HealBucketHandler.
 | 
						|
func TestHealBucketHandler(t *testing.T) {
 | 
						|
	adminTestBed, err := prepareAdminXLTestBed()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
 | 
						|
	}
 | 
						|
	defer adminTestBed.TearDown()
 | 
						|
 | 
						|
	err = adminTestBed.objLayer.MakeBucket("mybucket")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to make bucket - %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Delete bucket after running all test cases.
 | 
						|
	defer adminTestBed.objLayer.DeleteBucket("mybucket")
 | 
						|
 | 
						|
	testCases := []struct {
 | 
						|
		bucket     string
 | 
						|
		statusCode int
 | 
						|
		dryrun     string
 | 
						|
	}{
 | 
						|
		// 1. Valid test case.
 | 
						|
		{
 | 
						|
			bucket:     "mybucket",
 | 
						|
			statusCode: http.StatusOK,
 | 
						|
		},
 | 
						|
		// 2. Invalid bucket name.
 | 
						|
		{
 | 
						|
			bucket:     `invalid\\Bucket`,
 | 
						|
			statusCode: http.StatusBadRequest,
 | 
						|
		},
 | 
						|
		// 3. Bucket not found.
 | 
						|
		{
 | 
						|
			bucket:     "bucketnotfound",
 | 
						|
			statusCode: http.StatusNotFound,
 | 
						|
		},
 | 
						|
		// 4. Valid test case with dry-run.
 | 
						|
		{
 | 
						|
			bucket:     "mybucket",
 | 
						|
			statusCode: http.StatusOK,
 | 
						|
			dryrun:     "yes",
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for i, test := range testCases {
 | 
						|
		// Prepare query params.
 | 
						|
		queryVal := url.Values{}
 | 
						|
		queryVal.Set(string(mgmtBucket), test.bucket)
 | 
						|
		queryVal.Set("heal", "")
 | 
						|
		queryVal.Set(string(mgmtDryRun), test.dryrun)
 | 
						|
 | 
						|
		req, err := newTestRequest("POST", "/?"+queryVal.Encode(), 0, nil)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to construct heal bucket request - %v",
 | 
						|
				i+1, err)
 | 
						|
		}
 | 
						|
 | 
						|
		req.Header.Set(minioAdminOpHeader, "bucket")
 | 
						|
 | 
						|
		cred := serverConfig.GetCredential()
 | 
						|
		err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to sign heal bucket request - %v",
 | 
						|
				i+1, err)
 | 
						|
		}
 | 
						|
		rec := httptest.NewRecorder()
 | 
						|
		adminTestBed.mux.ServeHTTP(rec, req)
 | 
						|
		if test.statusCode != rec.Code {
 | 
						|
			t.Errorf("Test %d - Expected HTTP status code %d but received %d",
 | 
						|
				i+1, test.statusCode, rec.Code)
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestHealObjectHandler - Test for HealObjectHandler.
 | 
						|
func TestHealObjectHandler(t *testing.T) {
 | 
						|
	adminTestBed, err := prepareAdminXLTestBed()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
 | 
						|
	}
 | 
						|
	defer adminTestBed.TearDown()
 | 
						|
 | 
						|
	// Create an object myobject under bucket mybucket.
 | 
						|
	bucketName := "mybucket"
 | 
						|
	objName := "myobject"
 | 
						|
	err = adminTestBed.objLayer.MakeBucket(bucketName)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to make bucket %s - %v", bucketName, err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = adminTestBed.objLayer.PutObject(bucketName, objName,
 | 
						|
		int64(len("hello")), bytes.NewReader([]byte("hello")), nil, "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to create %s - %v", objName, err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Delete bucket and object after running all test cases.
 | 
						|
	defer func(objLayer ObjectLayer, bucketName, objName string) {
 | 
						|
		objLayer.DeleteObject(bucketName, objName)
 | 
						|
		objLayer.DeleteBucket(bucketName)
 | 
						|
	}(adminTestBed.objLayer, bucketName, objName)
 | 
						|
 | 
						|
	testCases := []struct {
 | 
						|
		bucket     string
 | 
						|
		object     string
 | 
						|
		dryrun     string
 | 
						|
		statusCode int
 | 
						|
	}{
 | 
						|
		// 1. Valid test case.
 | 
						|
		{
 | 
						|
			bucket:     bucketName,
 | 
						|
			object:     objName,
 | 
						|
			statusCode: http.StatusOK,
 | 
						|
		},
 | 
						|
		// 2. Invalid bucket name.
 | 
						|
		{
 | 
						|
			bucket:     `invalid\\Bucket`,
 | 
						|
			object:     "myobject",
 | 
						|
			statusCode: http.StatusBadRequest,
 | 
						|
		},
 | 
						|
		// 3. Bucket not found.
 | 
						|
		{
 | 
						|
			bucket:     "bucketnotfound",
 | 
						|
			object:     "myobject",
 | 
						|
			statusCode: http.StatusNotFound,
 | 
						|
		},
 | 
						|
		// 4. Invalid object name.
 | 
						|
		{
 | 
						|
			bucket:     bucketName,
 | 
						|
			object:     `invalid\\Object`,
 | 
						|
			statusCode: http.StatusBadRequest,
 | 
						|
		},
 | 
						|
		// 5. Object not found.
 | 
						|
		{
 | 
						|
			bucket:     bucketName,
 | 
						|
			object:     "objectnotfound",
 | 
						|
			statusCode: http.StatusNotFound,
 | 
						|
		},
 | 
						|
		// 6. Valid test case with dry-run.
 | 
						|
		{
 | 
						|
			bucket:     bucketName,
 | 
						|
			object:     objName,
 | 
						|
			dryrun:     "yes",
 | 
						|
			statusCode: http.StatusOK,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for i, test := range testCases {
 | 
						|
		// Prepare query params.
 | 
						|
		queryVal := url.Values{}
 | 
						|
		queryVal.Set(string(mgmtBucket), test.bucket)
 | 
						|
		queryVal.Set(string(mgmtObject), test.object)
 | 
						|
		queryVal.Set("heal", "")
 | 
						|
		queryVal.Set(string(mgmtDryRun), test.dryrun)
 | 
						|
 | 
						|
		req, err := newTestRequest("POST", "/?"+queryVal.Encode(), 0, nil)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to construct heal object request - %v", i+1, err)
 | 
						|
		}
 | 
						|
 | 
						|
		req.Header.Set(minioAdminOpHeader, "object")
 | 
						|
 | 
						|
		cred := serverConfig.GetCredential()
 | 
						|
		err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d - Failed to sign heal object request - %v", i+1, err)
 | 
						|
		}
 | 
						|
		rec := httptest.NewRecorder()
 | 
						|
		adminTestBed.mux.ServeHTTP(rec, req)
 | 
						|
		if test.statusCode != rec.Code {
 | 
						|
			t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.statusCode, rec.Code)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestHealFormatHandler - test for HealFormatHandler.
 | 
						|
func TestHealFormatHandler(t *testing.T) {
 | 
						|
	adminTestBed, err := prepareAdminXLTestBed()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
 | 
						|
	}
 | 
						|
	defer adminTestBed.TearDown()
 | 
						|
 | 
						|
	// Prepare query params for heal-format mgmt REST API.
 | 
						|
	queryVal := url.Values{}
 | 
						|
	queryVal.Set("heal", "")
 | 
						|
	req, err := newTestRequest("POST", "/?"+queryVal.Encode(), 0, nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to construct heal object request - %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Set x-minio-operation header to format.
 | 
						|
	req.Header.Set(minioAdminOpHeader, "format")
 | 
						|
 | 
						|
	// Sign the request using signature v4.
 | 
						|
	cred := serverConfig.GetCredential()
 | 
						|
	err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to sign heal object request - %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	rec := httptest.NewRecorder()
 | 
						|
	adminTestBed.mux.ServeHTTP(rec, req)
 | 
						|
	if rec.Code != http.StatusOK {
 | 
						|
		t.Errorf("Expected to succeed but failed with %d", rec.Code)
 | 
						|
	}
 | 
						|
}
 |