mirror of
https://github.com/minio/minio.git
synced 2025-04-18 01:40:11 -04:00
Implement mgmt REST APIs to heal storage format. (#3604)
* Implement heal format REST API handler * Implement admin peer rpc handler to re-initialize storage * Implement HealFormat API in pkg/madmin * Update pkg/madmin API.md to incl. HealFormat * Added unit tests for ReInitDisks rpc handler and HealFormatHandler
This commit is contained in:
parent
4e926b292f
commit
586058f079
@ -19,6 +19,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -374,6 +375,7 @@ func (adminAPI adminAPIHandlers) ListBucketsHealHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HealBucketHandler - POST /?heal&bucket=mybucket
|
// HealBucketHandler - POST /?heal&bucket=mybucket
|
||||||
|
// - x-minio-operation = bucket
|
||||||
// - bucket is mandatory query parameter
|
// - bucket is mandatory query parameter
|
||||||
// Heal a given bucket, if present.
|
// Heal a given bucket, if present.
|
||||||
func (adminAPI adminAPIHandlers) HealBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (adminAPI adminAPIHandlers) HealBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -425,6 +427,7 @@ func isDryRun(qval url.Values) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HealObjectHandler - POST /?heal&bucket=mybucket&object=myobject
|
// HealObjectHandler - POST /?heal&bucket=mybucket&object=myobject
|
||||||
|
// - x-minio-operation = object
|
||||||
// - bucket and object are both mandatory query parameters
|
// - bucket and object are both mandatory query parameters
|
||||||
// Heal a given object, if present.
|
// Heal a given object, if present.
|
||||||
func (adminAPI adminAPIHandlers) HealObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func (adminAPI adminAPIHandlers) HealObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -473,3 +476,69 @@ func (adminAPI adminAPIHandlers) HealObjectHandler(w http.ResponseWriter, r *htt
|
|||||||
// Return 200 on success.
|
// Return 200 on success.
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HealFormatHandler - POST /?heal
|
||||||
|
// - x-minio-operation = format
|
||||||
|
// - bucket and object are both mandatory query parameters
|
||||||
|
// Heal a given object, if present.
|
||||||
|
func (adminAPI adminAPIHandlers) HealFormatHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponse(w, ErrServerNotInitialized, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate request signature.
|
||||||
|
adminAPIErr := checkRequestAuthType(r, "", "", "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponse(w, adminAPIErr, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this setup is an erasure code backend, since
|
||||||
|
// heal-format is only applicable to single node XL and
|
||||||
|
// distributed XL setup.
|
||||||
|
if !globalIsXL {
|
||||||
|
writeErrorResponse(w, ErrNotImplemented, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new set of storage instances to heal format.json.
|
||||||
|
bootstrapDisks, err := initStorageDisks(globalEndpoints)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(traceError(err))
|
||||||
|
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heal format.json on available storage.
|
||||||
|
err = healFormatXL(bootstrapDisks)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(traceError(err))
|
||||||
|
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate new object layer with newly formatted storage.
|
||||||
|
newObjectAPI, err := newXLObjects(bootstrapDisks)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(traceError(err))
|
||||||
|
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set object layer with newly formatted storage to globalObjectAPI.
|
||||||
|
globalObjLayerMutex.Lock()
|
||||||
|
globalObjectAPI = newObjectAPI
|
||||||
|
globalObjLayerMutex.Unlock()
|
||||||
|
|
||||||
|
// Shutdown storage belonging to old object layer instance.
|
||||||
|
objectAPI.Shutdown()
|
||||||
|
|
||||||
|
// Inform peers to reinitialize storage with newly formatted storage.
|
||||||
|
reInitPeerDisks(globalAdminPeers)
|
||||||
|
|
||||||
|
// Return 200 on success.
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
@ -29,6 +29,79 @@ import (
|
|||||||
router "github.com/gorilla/mux"
|
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
|
// cmdType - Represents different service subcomands like status, stop
|
||||||
// and restart.
|
// and restart.
|
||||||
type cmdType int
|
type cmdType int
|
||||||
@ -115,17 +188,11 @@ func getServiceCmdRequest(cmd cmdType, cred credential, body []byte) (*http.Requ
|
|||||||
// testServicesCmdHandler - parametrizes service subcommand tests on
|
// testServicesCmdHandler - parametrizes service subcommand tests on
|
||||||
// cmdType value.
|
// cmdType value.
|
||||||
func testServicesCmdHandler(cmd cmdType, args map[string]interface{}, t *testing.T) {
|
func testServicesCmdHandler(cmd cmdType, args map[string]interface{}, t *testing.T) {
|
||||||
// reset globals.
|
adminTestBed, err := prepareAdminXLTestBed()
|
||||||
// this is to make sure that the tests are not affected by modified value.
|
|
||||||
resetTestGlobals()
|
|
||||||
// initialize NSLock.
|
|
||||||
initNSLock(false)
|
|
||||||
// Initialize configuration for access/secret credentials.
|
|
||||||
rootPath, err := newTestConfig(globalMinioDefaultRegion)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to initialize server config. %s", err)
|
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
||||||
}
|
}
|
||||||
defer removeAll(rootPath)
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls. Note: In a
|
// Initialize admin peers to make admin RPC calls. Note: In a
|
||||||
// single node setup, this degenerates to a simple function
|
// single node setup, this degenerates to a simple function
|
||||||
@ -139,29 +206,12 @@ func testServicesCmdHandler(cmd cmdType, args map[string]interface{}, t *testing
|
|||||||
globalMinioAddr = eps[0].Host
|
globalMinioAddr = eps[0].Host
|
||||||
initGlobalAdminPeers(eps)
|
initGlobalAdminPeers(eps)
|
||||||
|
|
||||||
if cmd == statusCmd {
|
|
||||||
// Initializing objectLayer and corresponding
|
|
||||||
// []StorageAPI since DiskInfo() method requires it.
|
|
||||||
objLayer, xlDirs, xlErr := prepareXL()
|
|
||||||
if xlErr != nil {
|
|
||||||
t.Fatalf("failed to initialize XL based object layer - %v.", xlErr)
|
|
||||||
}
|
|
||||||
defer removeRoots(xlDirs)
|
|
||||||
// Make objLayer available to all internal services via globalObjectAPI.
|
|
||||||
globalObjLayerMutex.Lock()
|
|
||||||
globalObjectAPI = objLayer
|
|
||||||
globalObjLayerMutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting up a go routine to simulate ServerMux's
|
// Setting up a go routine to simulate ServerMux's
|
||||||
// handleServiceSignals for stop and restart commands.
|
// handleServiceSignals for stop and restart commands.
|
||||||
if cmd == restartCmd {
|
if cmd == restartCmd {
|
||||||
go testServiceSignalReceiver(cmd, t)
|
go testServiceSignalReceiver(cmd, t)
|
||||||
}
|
}
|
||||||
credentials := serverConfig.GetCredential()
|
credentials := serverConfig.GetCredential()
|
||||||
adminRouter := router.NewRouter()
|
|
||||||
registerAdminRouter(adminRouter)
|
|
||||||
|
|
||||||
var body []byte
|
var body []byte
|
||||||
|
|
||||||
if cmd == setCreds {
|
if cmd == setCreds {
|
||||||
@ -174,7 +224,7 @@ func testServicesCmdHandler(cmd cmdType, args map[string]interface{}, t *testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
adminRouter.ServeHTTP(rec, req)
|
adminTestBed.mux.ServeHTTP(rec, req)
|
||||||
|
|
||||||
if cmd == statusCmd {
|
if cmd == statusCmd {
|
||||||
expectedInfo := newObjectLayerFn().StorageInfo()
|
expectedInfo := newObjectLayerFn().StorageInfo()
|
||||||
@ -232,17 +282,11 @@ func mkLockQueryVal(bucket, prefix, relTimeStr string) url.Values {
|
|||||||
|
|
||||||
// Test for locks list management REST API.
|
// Test for locks list management REST API.
|
||||||
func TestListLocksHandler(t *testing.T) {
|
func TestListLocksHandler(t *testing.T) {
|
||||||
// reset globals.
|
adminTestBed, err := prepareAdminXLTestBed()
|
||||||
// this is to make sure that the tests are not affected by modified globals.
|
|
||||||
resetTestGlobals()
|
|
||||||
// initialize NSLock.
|
|
||||||
initNSLock(false)
|
|
||||||
|
|
||||||
rootPath, err := newTestConfig(globalMinioDefaultRegion)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to initialize server config. %s", err)
|
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
||||||
}
|
}
|
||||||
defer removeAll(rootPath)
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
// Initialize admin peers to make admin RPC calls.
|
||||||
eps, err := parseStorageEndpoints([]string{"http://localhost"})
|
eps, err := parseStorageEndpoints([]string{"http://localhost"})
|
||||||
@ -254,10 +298,6 @@ func TestListLocksHandler(t *testing.T) {
|
|||||||
globalMinioAddr = eps[0].Host
|
globalMinioAddr = eps[0].Host
|
||||||
initGlobalAdminPeers(eps)
|
initGlobalAdminPeers(eps)
|
||||||
|
|
||||||
// Setup admin mgmt REST API handlers.
|
|
||||||
adminRouter := router.NewRouter()
|
|
||||||
registerAdminRouter(adminRouter)
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
bucket string
|
bucket string
|
||||||
prefix string
|
prefix string
|
||||||
@ -308,7 +348,7 @@ func TestListLocksHandler(t *testing.T) {
|
|||||||
t.Fatalf("Test %d - Failed to sign list locks request - %v", i+1, err)
|
t.Fatalf("Test %d - Failed to sign list locks request - %v", i+1, err)
|
||||||
}
|
}
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
adminRouter.ServeHTTP(rec, req)
|
adminTestBed.mux.ServeHTTP(rec, req)
|
||||||
if test.expectedStatus != rec.Code {
|
if test.expectedStatus != rec.Code {
|
||||||
t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.expectedStatus, rec.Code)
|
t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.expectedStatus, rec.Code)
|
||||||
}
|
}
|
||||||
@ -317,17 +357,11 @@ func TestListLocksHandler(t *testing.T) {
|
|||||||
|
|
||||||
// Test for locks clear management REST API.
|
// Test for locks clear management REST API.
|
||||||
func TestClearLocksHandler(t *testing.T) {
|
func TestClearLocksHandler(t *testing.T) {
|
||||||
// reset globals.
|
adminTestBed, err := prepareAdminXLTestBed()
|
||||||
// this is to make sure that the tests are not affected by modified globals.
|
|
||||||
resetTestGlobals()
|
|
||||||
// initialize NSLock.
|
|
||||||
initNSLock(false)
|
|
||||||
|
|
||||||
rootPath, err := newTestConfig(globalMinioDefaultRegion)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to initialize server config. %s", err)
|
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
||||||
}
|
}
|
||||||
defer removeAll(rootPath)
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
// Initialize admin peers to make admin RPC calls.
|
||||||
eps, err := parseStorageEndpoints([]string{"http://localhost"})
|
eps, err := parseStorageEndpoints([]string{"http://localhost"})
|
||||||
@ -336,10 +370,6 @@ func TestClearLocksHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
initGlobalAdminPeers(eps)
|
initGlobalAdminPeers(eps)
|
||||||
|
|
||||||
// Setup admin mgmt REST API handlers.
|
|
||||||
adminRouter := router.NewRouter()
|
|
||||||
registerAdminRouter(adminRouter)
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
bucket string
|
bucket string
|
||||||
prefix string
|
prefix string
|
||||||
@ -390,7 +420,7 @@ func TestClearLocksHandler(t *testing.T) {
|
|||||||
t.Fatalf("Test %d - Failed to sign clear locks request - %v", i+1, err)
|
t.Fatalf("Test %d - Failed to sign clear locks request - %v", i+1, err)
|
||||||
}
|
}
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
adminRouter.ServeHTTP(rec, req)
|
adminTestBed.mux.ServeHTTP(rec, req)
|
||||||
if test.expectedStatus != rec.Code {
|
if test.expectedStatus != rec.Code {
|
||||||
t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.expectedStatus, rec.Code)
|
t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.expectedStatus, rec.Code)
|
||||||
}
|
}
|
||||||
@ -556,36 +586,19 @@ func TestValidateHealQueryParams(t *testing.T) {
|
|||||||
|
|
||||||
// TestListObjectsHeal - Test for ListObjectsHealHandler.
|
// TestListObjectsHeal - Test for ListObjectsHealHandler.
|
||||||
func TestListObjectsHealHandler(t *testing.T) {
|
func TestListObjectsHealHandler(t *testing.T) {
|
||||||
rootPath, err := newTestConfig("us-east-1")
|
adminTestBed, err := prepareAdminXLTestBed()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to initialize server config. %s", err)
|
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
||||||
}
|
}
|
||||||
defer removeAll(rootPath)
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initializing objectLayer and corresponding []StorageAPI
|
err = adminTestBed.objLayer.MakeBucket("mybucket")
|
||||||
// since ListObjectsHeal() method requires it.
|
|
||||||
objLayer, xlDirs, xlErr := prepareXL()
|
|
||||||
if xlErr != nil {
|
|
||||||
t.Fatalf("failed to initialize XL based object layer - %v.", xlErr)
|
|
||||||
}
|
|
||||||
defer removeRoots(xlDirs)
|
|
||||||
|
|
||||||
err = objLayer.MakeBucket("mybucket")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to make bucket - %v", err)
|
t.Fatalf("Failed to make bucket - %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete bucket after running all test cases.
|
// Delete bucket after running all test cases.
|
||||||
defer objLayer.DeleteBucket("mybucket")
|
defer adminTestBed.objLayer.DeleteBucket("mybucket")
|
||||||
|
|
||||||
// Make objLayer available to all internal services via globalObjectAPI.
|
|
||||||
globalObjLayerMutex.Lock()
|
|
||||||
globalObjectAPI = objLayer
|
|
||||||
globalObjLayerMutex.Unlock()
|
|
||||||
|
|
||||||
// Setup admin mgmt REST API handlers.
|
|
||||||
adminRouter := router.NewRouter()
|
|
||||||
registerAdminRouter(adminRouter)
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
bucket string
|
bucket string
|
||||||
@ -695,7 +708,7 @@ func TestListObjectsHealHandler(t *testing.T) {
|
|||||||
t.Fatalf("Test %d - Failed to sign list objects needing heal request - %v", i+1, err)
|
t.Fatalf("Test %d - Failed to sign list objects needing heal request - %v", i+1, err)
|
||||||
}
|
}
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
adminRouter.ServeHTTP(rec, req)
|
adminTestBed.mux.ServeHTTP(rec, req)
|
||||||
if test.statusCode != rec.Code {
|
if test.statusCode != rec.Code {
|
||||||
t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.statusCode, rec.Code)
|
t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.statusCode, rec.Code)
|
||||||
}
|
}
|
||||||
@ -704,36 +717,19 @@ func TestListObjectsHealHandler(t *testing.T) {
|
|||||||
|
|
||||||
// TestHealBucketHandler - Test for HealBucketHandler.
|
// TestHealBucketHandler - Test for HealBucketHandler.
|
||||||
func TestHealBucketHandler(t *testing.T) {
|
func TestHealBucketHandler(t *testing.T) {
|
||||||
rootPath, err := newTestConfig("us-east-1")
|
adminTestBed, err := prepareAdminXLTestBed()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to initialize server config. %s", err)
|
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
||||||
}
|
}
|
||||||
defer removeAll(rootPath)
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initializing objectLayer and corresponding []StorageAPI
|
err = adminTestBed.objLayer.MakeBucket("mybucket")
|
||||||
// since MakeBucket() and DeleteBucket() methods requires it.
|
|
||||||
objLayer, xlDirs, xlErr := prepareXL()
|
|
||||||
if xlErr != nil {
|
|
||||||
t.Fatalf("failed to initialize XL based object layer - %v.", xlErr)
|
|
||||||
}
|
|
||||||
defer removeRoots(xlDirs)
|
|
||||||
|
|
||||||
err = objLayer.MakeBucket("mybucket")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to make bucket - %v", err)
|
t.Fatalf("Failed to make bucket - %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete bucket after running all test cases.
|
// Delete bucket after running all test cases.
|
||||||
defer objLayer.DeleteBucket("mybucket")
|
defer adminTestBed.objLayer.DeleteBucket("mybucket")
|
||||||
|
|
||||||
// Make objLayer available to all internal services via globalObjectAPI.
|
|
||||||
globalObjLayerMutex.Lock()
|
|
||||||
globalObjectAPI = objLayer
|
|
||||||
globalObjLayerMutex.Unlock()
|
|
||||||
|
|
||||||
// Setup admin mgmt REST API handlers.
|
|
||||||
adminRouter := router.NewRouter()
|
|
||||||
registerAdminRouter(adminRouter)
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
bucket string
|
bucket string
|
||||||
@ -771,7 +767,8 @@ func TestHealBucketHandler(t *testing.T) {
|
|||||||
|
|
||||||
req, err := newTestRequest("POST", "/?"+queryVal.Encode(), 0, nil)
|
req, err := newTestRequest("POST", "/?"+queryVal.Encode(), 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d - Failed to construct heal bucket request - %v", i+1, err)
|
t.Fatalf("Test %d - Failed to construct heal bucket request - %v",
|
||||||
|
i+1, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set(minioAdminOpHeader, "bucket")
|
req.Header.Set(minioAdminOpHeader, "bucket")
|
||||||
@ -779,12 +776,14 @@ func TestHealBucketHandler(t *testing.T) {
|
|||||||
cred := serverConfig.GetCredential()
|
cred := serverConfig.GetCredential()
|
||||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d - Failed to sign heal bucket request - %v", i+1, err)
|
t.Fatalf("Test %d - Failed to sign heal bucket request - %v",
|
||||||
|
i+1, err)
|
||||||
}
|
}
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
adminRouter.ServeHTTP(rec, req)
|
adminTestBed.mux.ServeHTTP(rec, req)
|
||||||
if test.statusCode != rec.Code {
|
if test.statusCode != rec.Code {
|
||||||
t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, test.statusCode, rec.Code)
|
t.Errorf("Test %d - Expected HTTP status code %d but received %d",
|
||||||
|
i+1, test.statusCode, rec.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -792,29 +791,22 @@ func TestHealBucketHandler(t *testing.T) {
|
|||||||
|
|
||||||
// TestHealObjectHandler - Test for HealObjectHandler.
|
// TestHealObjectHandler - Test for HealObjectHandler.
|
||||||
func TestHealObjectHandler(t *testing.T) {
|
func TestHealObjectHandler(t *testing.T) {
|
||||||
rootPath, err := newTestConfig("us-east-1")
|
adminTestBed, err := prepareAdminXLTestBed()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to initialize server config. %s", err)
|
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
||||||
}
|
}
|
||||||
defer removeAll(rootPath)
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initializing objectLayer and corresponding []StorageAPI
|
|
||||||
// since MakeBucket(), PutObject() and DeleteBucket() method requires it.
|
|
||||||
objLayer, xlDirs, xlErr := prepareXL()
|
|
||||||
if xlErr != nil {
|
|
||||||
t.Fatalf("failed to initialize XL based object layer - %v.", xlErr)
|
|
||||||
}
|
|
||||||
defer removeRoots(xlDirs)
|
|
||||||
|
|
||||||
// Create an object myobject under bucket mybucket.
|
// Create an object myobject under bucket mybucket.
|
||||||
bucketName := "mybucket"
|
bucketName := "mybucket"
|
||||||
objName := "myobject"
|
objName := "myobject"
|
||||||
err = objLayer.MakeBucket(bucketName)
|
err = adminTestBed.objLayer.MakeBucket(bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to make bucket %s - %v", bucketName, err)
|
t.Fatalf("Failed to make bucket %s - %v", bucketName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = objLayer.PutObject(bucketName, objName, int64(len("hello")), bytes.NewReader([]byte("hello")), nil, "")
|
_, err = adminTestBed.objLayer.PutObject(bucketName, objName,
|
||||||
|
int64(len("hello")), bytes.NewReader([]byte("hello")), nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create %s - %v", objName, err)
|
t.Fatalf("Failed to create %s - %v", objName, err)
|
||||||
}
|
}
|
||||||
@ -823,16 +815,7 @@ func TestHealObjectHandler(t *testing.T) {
|
|||||||
defer func(objLayer ObjectLayer, bucketName, objName string) {
|
defer func(objLayer ObjectLayer, bucketName, objName string) {
|
||||||
objLayer.DeleteObject(bucketName, objName)
|
objLayer.DeleteObject(bucketName, objName)
|
||||||
objLayer.DeleteBucket(bucketName)
|
objLayer.DeleteBucket(bucketName)
|
||||||
}(objLayer, bucketName, objName)
|
}(adminTestBed.objLayer, bucketName, objName)
|
||||||
|
|
||||||
// Make objLayer available to all internal services via globalObjectAPI.
|
|
||||||
globalObjLayerMutex.Lock()
|
|
||||||
globalObjectAPI = objLayer
|
|
||||||
globalObjLayerMutex.Unlock()
|
|
||||||
|
|
||||||
// Setup admin mgmt REST API handlers.
|
|
||||||
adminRouter := router.NewRouter()
|
|
||||||
registerAdminRouter(adminRouter)
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
bucket string
|
bucket string
|
||||||
@ -899,9 +882,42 @@ func TestHealObjectHandler(t *testing.T) {
|
|||||||
t.Fatalf("Test %d - Failed to sign heal object request - %v", i+1, err)
|
t.Fatalf("Test %d - Failed to sign heal object request - %v", i+1, err)
|
||||||
}
|
}
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
adminRouter.ServeHTTP(rec, req)
|
adminTestBed.mux.ServeHTTP(rec, req)
|
||||||
if test.statusCode != rec.Code {
|
if test.statusCode != rec.Code {
|
||||||
t.Errorf("Test %d - Expected HTTP status code %d but received %d", i+1, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -57,4 +57,6 @@ func registerAdminRouter(mux *router.Router) {
|
|||||||
adminRouter.Methods("POST").Queries("heal", "").Headers(minioAdminOpHeader, "bucket").HandlerFunc(adminAPI.HealBucketHandler)
|
adminRouter.Methods("POST").Queries("heal", "").Headers(minioAdminOpHeader, "bucket").HandlerFunc(adminAPI.HealBucketHandler)
|
||||||
// Heal Objects.
|
// Heal Objects.
|
||||||
adminRouter.Methods("POST").Queries("heal", "").Headers(minioAdminOpHeader, "object").HandlerFunc(adminAPI.HealObjectHandler)
|
adminRouter.Methods("POST").Queries("heal", "").Headers(minioAdminOpHeader, "object").HandlerFunc(adminAPI.HealObjectHandler)
|
||||||
|
// Heal Format.
|
||||||
|
adminRouter.Methods("POST").Queries("heal", "").Headers(minioAdminOpHeader, "format").HandlerFunc(adminAPI.HealFormatHandler)
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ type remoteAdminClient struct {
|
|||||||
type adminCmdRunner interface {
|
type adminCmdRunner interface {
|
||||||
Restart() error
|
Restart() error
|
||||||
ListLocks(bucket, prefix string, relTime time.Duration) ([]VolumeLockInfo, error)
|
ListLocks(bucket, prefix string, relTime time.Duration) ([]VolumeLockInfo, error)
|
||||||
|
ReInitDisks() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart - Sends a message over channel to the go-routine
|
// Restart - Sends a message over channel to the go-routine
|
||||||
@ -73,6 +74,20 @@ func (rc remoteAdminClient) ListLocks(bucket, prefix string, relTime time.Durati
|
|||||||
return reply.volLocks, nil
|
return reply.volLocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReInitDisks - There is nothing to do here, heal format REST API
|
||||||
|
// handler has already formatted and reinitialized the local disks.
|
||||||
|
func (lc localAdminClient) ReInitDisks() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReInitDisks - Signals peers via RPC to reinitialize their disks and
|
||||||
|
// object layer.
|
||||||
|
func (rc remoteAdminClient) ReInitDisks() error {
|
||||||
|
args := AuthRPCArgs{}
|
||||||
|
reply := AuthRPCReply{}
|
||||||
|
return rc.Call("Admin.ReInitDisks", &args, &reply)
|
||||||
|
}
|
||||||
|
|
||||||
// adminPeer - represents an entity that implements Restart methods.
|
// adminPeer - represents an entity that implements Restart methods.
|
||||||
type adminPeer struct {
|
type adminPeer struct {
|
||||||
addr string
|
addr string
|
||||||
@ -159,6 +174,8 @@ func sendServiceCmd(cps adminPeers, cmd serviceSignal) {
|
|||||||
errs[0] = invokeServiceCmd(cps[0], cmd)
|
errs[0] = invokeServiceCmd(cps[0], cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listPeerLocksInfo - fetch list of locks held on the given bucket,
|
||||||
|
// matching prefix older than relTime from all peer servers.
|
||||||
func listPeerLocksInfo(peers adminPeers, bucket, prefix string, relTime time.Duration) ([]VolumeLockInfo, error) {
|
func listPeerLocksInfo(peers adminPeers, bucket, prefix string, relTime time.Duration) ([]VolumeLockInfo, error) {
|
||||||
// Used to aggregate volume lock information from all nodes.
|
// Used to aggregate volume lock information from all nodes.
|
||||||
allLocks := make([][]VolumeLockInfo, len(peers))
|
allLocks := make([][]VolumeLockInfo, len(peers))
|
||||||
@ -206,3 +223,21 @@ func listPeerLocksInfo(peers adminPeers, bucket, prefix string, relTime time.Dur
|
|||||||
}
|
}
|
||||||
return groupedLockInfos, nil
|
return groupedLockInfos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reInitPeerDisks - reinitialize disks and object layer on peer servers to use the new format.
|
||||||
|
func reInitPeerDisks(peers adminPeers) error {
|
||||||
|
errs := make([]error, len(peers))
|
||||||
|
|
||||||
|
// Send ReInitDisks RPC call to all nodes.
|
||||||
|
// for local adminPeer this is a no-op.
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for i, peer := range peers {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int, peer adminPeer) {
|
||||||
|
defer wg.Done()
|
||||||
|
errs[idx] = peer.cmdRunner.ReInitDisks()
|
||||||
|
}(i, peer)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/rpc"
|
"net/rpc"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -25,6 +26,8 @@ import (
|
|||||||
|
|
||||||
const adminPath = "/admin"
|
const adminPath = "/admin"
|
||||||
|
|
||||||
|
var errUnsupportedBackend = errors.New("not supported for non erasure-code backend")
|
||||||
|
|
||||||
// adminCmd - exports RPC methods for service status, stop and
|
// adminCmd - exports RPC methods for service status, stop and
|
||||||
// restart commands.
|
// restart commands.
|
||||||
type adminCmd struct {
|
type adminCmd struct {
|
||||||
@ -57,11 +60,51 @@ func (s *adminCmd) Restart(args *AuthRPCArgs, reply *AuthRPCReply) error {
|
|||||||
|
|
||||||
// ListLocks - lists locks held by requests handled by this server instance.
|
// ListLocks - lists locks held by requests handled by this server instance.
|
||||||
func (s *adminCmd) ListLocks(query *ListLocksQuery, reply *ListLocksReply) error {
|
func (s *adminCmd) ListLocks(query *ListLocksQuery, reply *ListLocksReply) error {
|
||||||
|
if err := query.IsAuthenticated(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
volLocks := listLocksInfo(query.bucket, query.prefix, query.relTime)
|
volLocks := listLocksInfo(query.bucket, query.prefix, query.relTime)
|
||||||
*reply = ListLocksReply{volLocks: volLocks}
|
*reply = ListLocksReply{volLocks: volLocks}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReInitDisk - reinitialize storage disks and object layer to use the
|
||||||
|
// new format.
|
||||||
|
func (s *adminCmd) ReInitDisks(args *AuthRPCArgs, reply *AuthRPCReply) error {
|
||||||
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !globalIsXL {
|
||||||
|
return errUnsupportedBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current object layer instance.
|
||||||
|
objLayer := newObjectLayerFn()
|
||||||
|
|
||||||
|
// Initialize new disks to include the newly formatted disks.
|
||||||
|
bootstrapDisks, err := initStorageDisks(globalEndpoints)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize new object layer with newly formatted disks.
|
||||||
|
newObjectAPI, err := newXLObjects(bootstrapDisks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace object layer with newly formatted storage.
|
||||||
|
globalObjLayerMutex.Lock()
|
||||||
|
globalObjectAPI = newObjectAPI
|
||||||
|
globalObjLayerMutex.Unlock()
|
||||||
|
|
||||||
|
// Shutdown storage belonging to old object layer instance.
|
||||||
|
objLayer.Shutdown()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// registerAdminRPCRouter - registers RPC methods for service status,
|
// registerAdminRPCRouter - registers RPC methods for service status,
|
||||||
// stop and restart commands.
|
// stop and restart commands.
|
||||||
func registerAdminRPCRouter(mux *router.Router) error {
|
func registerAdminRPCRouter(mux *router.Router) error {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -61,6 +62,85 @@ func testAdminCmd(cmd cmdType, t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAdminRestart - test for Admin.Restart RPC service.
|
||||||
func TestAdminRestart(t *testing.T) {
|
func TestAdminRestart(t *testing.T) {
|
||||||
testAdminCmd(restartCmd, t)
|
testAdminCmd(restartCmd, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestReInitDisks - test for Admin.ReInitDisks RPC service.
|
||||||
|
func TestReInitDisks(t *testing.T) {
|
||||||
|
// Reset global variables to start afresh.
|
||||||
|
resetTestGlobals()
|
||||||
|
|
||||||
|
rootPath, err := newTestConfig("us-east-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to initialize server config. %s", err)
|
||||||
|
}
|
||||||
|
defer removeAll(rootPath)
|
||||||
|
|
||||||
|
// Initializing objectLayer for HealFormatHandler.
|
||||||
|
_, xlDirs, xlErr := initTestXLObjLayer()
|
||||||
|
if xlErr != nil {
|
||||||
|
t.Fatalf("failed to initialize XL based object layer - %v.", xlErr)
|
||||||
|
}
|
||||||
|
defer removeRoots(xlDirs)
|
||||||
|
|
||||||
|
// Set globalEndpoints for a single node XL setup.
|
||||||
|
for _, xlDir := range xlDirs {
|
||||||
|
globalEndpoints = append(globalEndpoints, &url.URL{Path: xlDir})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup admin rpc server for an XL backend.
|
||||||
|
globalIsXL = true
|
||||||
|
adminServer := adminCmd{}
|
||||||
|
creds := serverConfig.GetCredential()
|
||||||
|
args := LoginRPCArgs{
|
||||||
|
Username: creds.AccessKey,
|
||||||
|
Password: creds.SecretKey,
|
||||||
|
Version: Version,
|
||||||
|
RequestTime: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
reply := LoginRPCReply{}
|
||||||
|
err = adminServer.Login(&args, &reply)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to login to admin server - %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authArgs := AuthRPCArgs{
|
||||||
|
AuthToken: reply.AuthToken,
|
||||||
|
RequestTime: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
authReply := AuthRPCReply{}
|
||||||
|
|
||||||
|
err = adminServer.ReInitDisks(&authArgs, &authReply)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected to pass, but failed with %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative test case with admin rpc server setup for FS.
|
||||||
|
globalIsXL = false
|
||||||
|
fsAdminServer := adminCmd{}
|
||||||
|
fsArgs := LoginRPCArgs{
|
||||||
|
Username: creds.AccessKey,
|
||||||
|
Password: creds.SecretKey,
|
||||||
|
Version: Version,
|
||||||
|
RequestTime: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
fsReply := LoginRPCReply{}
|
||||||
|
err = fsAdminServer.Login(&fsArgs, &fsReply)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to login to fs admin server - %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authArgs = AuthRPCArgs{
|
||||||
|
AuthToken: fsReply.AuthToken,
|
||||||
|
RequestTime: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
authReply = AuthRPCReply{}
|
||||||
|
// Attempt ReInitDisks service on a FS backend.
|
||||||
|
err = fsAdminServer.ReInitDisks(&authArgs, &authReply)
|
||||||
|
if err != errUnsupportedBackend {
|
||||||
|
t.Errorf("Expected to fail with %v, but received %v",
|
||||||
|
errUnsupportedBackend, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@ -70,6 +71,9 @@ var (
|
|||||||
// Indicates if the running minio server is distributed setup.
|
// Indicates if the running minio server is distributed setup.
|
||||||
globalIsDistXL = false
|
globalIsDistXL = false
|
||||||
|
|
||||||
|
// Indicates if the running minio server is an erasure-code backend.
|
||||||
|
globalIsXL = false
|
||||||
|
|
||||||
// This flag is set to 'true' by default, it is set to `false`
|
// This flag is set to 'true' by default, it is set to `false`
|
||||||
// when MINIO_BROWSER env is set to 'off'.
|
// when MINIO_BROWSER env is set to 'off'.
|
||||||
globalIsBrowserEnabled = !strings.EqualFold(os.Getenv("MINIO_BROWSER"), "off")
|
globalIsBrowserEnabled = !strings.EqualFold(os.Getenv("MINIO_BROWSER"), "off")
|
||||||
@ -112,6 +116,9 @@ var (
|
|||||||
// Secret key passed from the environment
|
// Secret key passed from the environment
|
||||||
globalEnvSecretKey = os.Getenv("MINIO_SECRET_KEY")
|
globalEnvSecretKey = os.Getenv("MINIO_SECRET_KEY")
|
||||||
|
|
||||||
|
// url.URL endpoints of disks that belong to the object storage.
|
||||||
|
globalEndpoints = []*url.URL{}
|
||||||
|
|
||||||
// Add new variable global values here.
|
// Add new variable global values here.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -418,6 +418,12 @@ func serverMain(c *cli.Context) {
|
|||||||
fatalIf(initDsyncNodes(endpoints), "Unable to initialize distributed locking clients")
|
fatalIf(initDsyncNodes(endpoints), "Unable to initialize distributed locking clients")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set globalIsXL if erasure code backend is about to be
|
||||||
|
// initialized for the given endpoints.
|
||||||
|
if len(endpoints) > 1 {
|
||||||
|
globalIsXL = true
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize name space lock.
|
// Initialize name space lock.
|
||||||
initNSLock(globalIsDistXL)
|
initNSLock(globalIsDistXL)
|
||||||
|
|
||||||
@ -453,6 +459,9 @@ func serverMain(c *cli.Context) {
|
|||||||
fatalIf(apiServer.ListenAndServe(cert, key), "Failed to start minio server.")
|
fatalIf(apiServer.ListenAndServe(cert, key), "Failed to start minio server.")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Set endpoints of []*url.URL type to globalEndpoints.
|
||||||
|
globalEndpoints = endpoints
|
||||||
|
|
||||||
newObject, err := newObjectLayer(srvConfig)
|
newObject, err := newObjectLayer(srvConfig)
|
||||||
fatalIf(err, "Initializing object layer failed")
|
fatalIf(err, "Initializing object layer failed")
|
||||||
|
|
||||||
|
@ -488,6 +488,14 @@ func resetGlobalEventnotify() {
|
|||||||
globalEventNotifier = nil
|
globalEventNotifier = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetGlobalEndpoints() {
|
||||||
|
globalEndpoints = []*url.URL{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetGlobalIsXL() {
|
||||||
|
globalIsXL = false
|
||||||
|
}
|
||||||
|
|
||||||
// Resets all the globals used modified in tests.
|
// Resets all the globals used modified in tests.
|
||||||
// Resetting ensures that the changes made to globals by one test doesn't affect others.
|
// Resetting ensures that the changes made to globals by one test doesn't affect others.
|
||||||
func resetTestGlobals() {
|
func resetTestGlobals() {
|
||||||
@ -501,6 +509,10 @@ func resetTestGlobals() {
|
|||||||
resetGlobalNSLock()
|
resetGlobalNSLock()
|
||||||
// Reset global event notifier.
|
// Reset global event notifier.
|
||||||
resetGlobalEventnotify()
|
resetGlobalEventnotify()
|
||||||
|
// Reset global endpoints.
|
||||||
|
resetGlobalEndpoints()
|
||||||
|
// Reset global isXL flag.
|
||||||
|
resetGlobalIsXL()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the server for the test run.
|
// Configure the server for the test run.
|
||||||
|
@ -163,6 +163,14 @@ func newXLObjects(storageDisks []StorageAPI) (ObjectLayer, error) {
|
|||||||
// Shutdown function for object storage interface.
|
// Shutdown function for object storage interface.
|
||||||
func (xl xlObjects) Shutdown() error {
|
func (xl xlObjects) Shutdown() error {
|
||||||
// Add any object layer shutdown activities here.
|
// Add any object layer shutdown activities here.
|
||||||
|
for _, disk := range xl.storageDisks {
|
||||||
|
// This closes storage rpc client connections if any.
|
||||||
|
// Otherwise this is a no-op.
|
||||||
|
if disk == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
disk.Close()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +38,11 @@ func main() {
|
|||||||
|
|
||||||
| Service operations|LockInfo operations|Healing operations|
|
| Service operations|LockInfo operations|Healing operations|
|
||||||
|:---|:---|:---|
|
|:---|:---|:---|
|
||||||
|[`ServiceStatus`](#ServiceStatus)| | |
|
|[`ServiceStatus`](#ServiceStatus)| [`ListLocks`](#ListLocks)| [`ListObjectsHeal`](#ListObjectsHeal)|
|
||||||
|[`ServiceRestart`](#ServiceRestart)| | |
|
|[`ServiceRestart`](#ServiceRestart)| [`ClearLocks`](#ClearLocks)| [`ListBucketsHeal`](#ListBucketsHeal)|
|
||||||
|
| | |[`HealBucket`](#HealBucket) |
|
||||||
|
| | |[`HealObject`](#HealObject)|
|
||||||
|
| | |[`HealFormat`](#HealFormat)|
|
||||||
|
|
||||||
## 1. Constructor
|
## 1. Constructor
|
||||||
<a name="Minio"></a>
|
<a name="Minio"></a>
|
||||||
@ -185,8 +188,8 @@ __Example__
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<a name="ListBucketsList"></a>
|
<a name="ListBucketsHeal"></a>
|
||||||
### ListBucketsList() error
|
### ListBucketsHeal() error
|
||||||
If successful returns information on the list of buckets that need healing.
|
If successful returns information on the list of buckets that need healing.
|
||||||
|
|
||||||
__Example__
|
__Example__
|
||||||
@ -244,3 +247,19 @@ __Example__
|
|||||||
log.Println("successfully healed mybucket/myobject")
|
log.Println("successfully healed mybucket/myobject")
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<a name="HealFormat"></a>
|
||||||
|
### HealFormat() error
|
||||||
|
Heal storage format on available disks. This is used when disks were replaced or were found with missing format. This is supported only for erasure-coded backend.
|
||||||
|
|
||||||
|
__Example__
|
||||||
|
|
||||||
|
``` go
|
||||||
|
err := madmClnt.HealFormat()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("successfully healed storage format on available disks.")
|
||||||
|
|
||||||
|
```
|
||||||
|
49
pkg/madmin/examples/heal-format.go
Normal file
49
pkg/madmin/examples/heal-format.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||||
|
// dummy values, please replace them with original values.
|
||||||
|
|
||||||
|
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||||
|
// dummy values, please replace them with original values.
|
||||||
|
|
||||||
|
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||||
|
// New returns an Minio Admin client object.
|
||||||
|
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heal storage format on available disks.
|
||||||
|
err = madmClnt.HealFormat()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("successfully healed storage format on available disks.")
|
||||||
|
}
|
@ -403,3 +403,32 @@ func (adm *AdminClient) HealObject(bucket, object string, dryrun bool) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HealFormat - heal storage format on available disks.
|
||||||
|
func (adm *AdminClient) HealFormat() error {
|
||||||
|
queryVal := url.Values{}
|
||||||
|
queryVal.Set("heal", "")
|
||||||
|
|
||||||
|
// Set x-minio-operation to format.
|
||||||
|
hdrs := make(http.Header)
|
||||||
|
hdrs.Set(minioAdminOpHeader, "format")
|
||||||
|
|
||||||
|
reqData := requestData{
|
||||||
|
queryValues: queryVal,
|
||||||
|
customHeaders: hdrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute POST on /?heal to heal storage format.
|
||||||
|
resp, err := adm.executeMethod("POST", reqData)
|
||||||
|
|
||||||
|
defer closeResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return errors.New("Got HTTP Status: " + resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user