mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
Implement SetConfig admin API handler. (#3792)
This commit is contained in:
parent
dce0345f8f
commit
c9619673fb
@ -17,8 +17,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -27,7 +29,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minioAdminOpHeader = "X-Minio-Operation"
|
minioAdminOpHeader = "X-Minio-Operation"
|
||||||
|
minioConfigTmpFormat = "config-%s.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type-safe query params.
|
// Type-safe query params.
|
||||||
@ -694,10 +697,127 @@ func (adminAPI adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http
|
|||||||
// returns local config.json.
|
// returns local config.json.
|
||||||
configBytes, err := getPeerConfig(globalAdminPeers)
|
configBytes, err := getPeerConfig(globalAdminPeers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
|
||||||
errorIf(err, "Failed to get config from peers")
|
errorIf(err, "Failed to get config from peers")
|
||||||
|
writeErrorResponse(w, toAdminAPIErrCode(err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeSuccessResponseJSON(w, configBytes)
|
writeSuccessResponseJSON(w, configBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toAdminAPIErrCode - converts errXLWriteQuorum error to admin API
|
||||||
|
// specific error.
|
||||||
|
func toAdminAPIErrCode(err error) APIErrorCode {
|
||||||
|
switch err {
|
||||||
|
case errXLWriteQuorum:
|
||||||
|
return ErrAdminConfigNoQuorum
|
||||||
|
}
|
||||||
|
return toAPIErrorCode(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfigResult - represents detailed results of a set-config
|
||||||
|
// operation.
|
||||||
|
type nodeSummary struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Err string `json:"err"`
|
||||||
|
}
|
||||||
|
type setConfigResult struct {
|
||||||
|
NodeResults []nodeSummary `json:"nodeResults"`
|
||||||
|
Status bool `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeSetConfigResponse - writes setConfigResult value as json depending on the status.
|
||||||
|
func writeSetConfigResponse(w http.ResponseWriter, peers adminPeers, errs []error, status bool, reqURL *url.URL) {
|
||||||
|
var nodeResults []nodeSummary
|
||||||
|
// Build nodeResults based on error values received during
|
||||||
|
// set-config operation.
|
||||||
|
for i := range errs {
|
||||||
|
nodeResults = append(nodeResults, nodeSummary{
|
||||||
|
Name: peers[i].addr,
|
||||||
|
Err: fmt.Sprintf("%v", errs[i]),
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
result := setConfigResult{
|
||||||
|
Status: status,
|
||||||
|
NodeResults: nodeResults,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following elaborate json encoding is to avoid escaping
|
||||||
|
// '<', '>' in <nil>. Note: json.Encoder.Encode() adds a
|
||||||
|
// gratuitous "\n".
|
||||||
|
var resultBuf bytes.Buffer
|
||||||
|
enc := json.NewEncoder(&resultBuf)
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
jsonErr := enc.Encode(result)
|
||||||
|
if jsonErr != nil {
|
||||||
|
writeErrorResponse(w, toAPIErrorCode(jsonErr), reqURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, resultBuf.Bytes())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfigHandler - PUT /?config
|
||||||
|
// - x-minio-operation = set
|
||||||
|
func (adminAPI adminAPIHandlers) SetConfigHandler(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read configuration bytes from request body.
|
||||||
|
configBytes, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Failed to read config from request body.")
|
||||||
|
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write config received from request onto a temporary file on
|
||||||
|
// all nodes.
|
||||||
|
tmpFileName := fmt.Sprintf(minioConfigTmpFormat, mustGetUUID())
|
||||||
|
errs := writeTmpConfigPeers(globalAdminPeers, tmpFileName, configBytes)
|
||||||
|
|
||||||
|
// Check if the operation succeeded in quorum or more nodes.
|
||||||
|
rErr := reduceWriteQuorumErrs(errs, nil, len(globalAdminPeers)/2+1)
|
||||||
|
if rErr != nil {
|
||||||
|
writeSetConfigResponse(w, globalAdminPeers, errs, false, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take a lock on minio/config.json. NB minio is a reserved
|
||||||
|
// bucket name and wouldn't conflict with normal object
|
||||||
|
// operations.
|
||||||
|
configLock := globalNSMutex.NewNSLock(minioReservedBucket, globalMinioConfigFile)
|
||||||
|
configLock.Lock()
|
||||||
|
defer configLock.Unlock()
|
||||||
|
|
||||||
|
// Rename the temporary config file to config.json
|
||||||
|
errs = commitConfigPeers(globalAdminPeers, tmpFileName)
|
||||||
|
rErr = reduceWriteQuorumErrs(errs, nil, len(globalAdminPeers)/2+1)
|
||||||
|
if rErr != nil {
|
||||||
|
writeSetConfigResponse(w, globalAdminPeers, errs, false, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverMux (cmd/server-mux.go) implements graceful shutdown,
|
||||||
|
// where all listeners are closed and process restart/shutdown
|
||||||
|
// happens after 5s or completion of all ongoing http
|
||||||
|
// requests, whichever is earlier.
|
||||||
|
writeSetConfigResponse(w, globalAdminPeers, errs, true, r.URL)
|
||||||
|
|
||||||
|
// Restart all node for the modified config to take effect.
|
||||||
|
sendServiceCmd(globalAdminPeers, serviceRestart)
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -30,6 +31,102 @@ import (
|
|||||||
router "github.com/gorilla/mux"
|
router "github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var configJSON = []byte(`{
|
||||||
|
"version": "13",
|
||||||
|
"credential": {
|
||||||
|
"accessKey": "minio",
|
||||||
|
"secretKey": "minio123"
|
||||||
|
},
|
||||||
|
"region": "us-west-1",
|
||||||
|
"logger": {
|
||||||
|
"console": {
|
||||||
|
"enable": true,
|
||||||
|
"level": "fatal"
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"enable": false,
|
||||||
|
"fileName": "",
|
||||||
|
"level": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notify": {
|
||||||
|
"amqp": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"url": "",
|
||||||
|
"exchange": "",
|
||||||
|
"routingKey": "",
|
||||||
|
"exchangeType": "",
|
||||||
|
"mandatory": false,
|
||||||
|
"immediate": false,
|
||||||
|
"durable": false,
|
||||||
|
"internal": false,
|
||||||
|
"noWait": false,
|
||||||
|
"autoDeleted": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nats": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"address": "",
|
||||||
|
"subject": "",
|
||||||
|
"username": "",
|
||||||
|
"password": "",
|
||||||
|
"token": "",
|
||||||
|
"secure": false,
|
||||||
|
"pingInterval": 0,
|
||||||
|
"streaming": {
|
||||||
|
"enable": false,
|
||||||
|
"clusterID": "",
|
||||||
|
"clientID": "",
|
||||||
|
"async": false,
|
||||||
|
"maxPubAcksInflight": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"elasticsearch": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"url": "",
|
||||||
|
"index": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redis": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"address": "",
|
||||||
|
"password": "",
|
||||||
|
"key": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postgresql": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"connectionString": "",
|
||||||
|
"table": "",
|
||||||
|
"host": "",
|
||||||
|
"port": "",
|
||||||
|
"user": "",
|
||||||
|
"password": "",
|
||||||
|
"database": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"kafka": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"brokers": null,
|
||||||
|
"topic": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhook": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"endpoint": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
// adminXLTestBed - encapsulates subsystems that need to be setup for
|
// adminXLTestBed - encapsulates subsystems that need to be setup for
|
||||||
// admin-handler unit tests.
|
// admin-handler unit tests.
|
||||||
type adminXLTestBed struct {
|
type adminXLTestBed struct {
|
||||||
@ -1033,3 +1130,159 @@ func TestGetConfigHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSetConfigHandler - test for SetConfigHandler.
|
||||||
|
func TestSetConfigHandler(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://127.0.0.1"})
|
||||||
|
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)
|
||||||
|
|
||||||
|
// SetConfigHandler restarts minio setup - need to start a
|
||||||
|
// signal receiver to receive on globalServiceSignalCh.
|
||||||
|
go testServiceSignalReceiver(restartCmd, t)
|
||||||
|
|
||||||
|
// Prepare query params for set-config mgmt REST API.
|
||||||
|
queryVal := url.Values{}
|
||||||
|
queryVal.Set("config", "")
|
||||||
|
|
||||||
|
req, err := newTestRequest("PUT", "/?"+queryVal.Encode(), int64(len(configJSON)), bytes.NewReader(configJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to construct get-config object request - %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set x-minio-operation header to set.
|
||||||
|
req.Header.Set(minioAdminOpHeader, "set")
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := setConfigResult{}
|
||||||
|
err = json.NewDecoder(rec.Body).Decode(&result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode set config result json %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Status != true {
|
||||||
|
t.Error("Expected set-config to succeed, but failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestToAdminAPIErr - test for toAdminAPIErr helper function.
|
||||||
|
func TestToAdminAPIErr(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
err error
|
||||||
|
expectedAPIErr APIErrorCode
|
||||||
|
}{
|
||||||
|
// 1. Server not in quorum.
|
||||||
|
{
|
||||||
|
err: errXLWriteQuorum,
|
||||||
|
expectedAPIErr: ErrAdminConfigNoQuorum,
|
||||||
|
},
|
||||||
|
// 2. No error.
|
||||||
|
{
|
||||||
|
err: nil,
|
||||||
|
expectedAPIErr: ErrNone,
|
||||||
|
},
|
||||||
|
// 3. Non-admin API specific error.
|
||||||
|
{
|
||||||
|
err: errDiskNotFound,
|
||||||
|
expectedAPIErr: toAPIErrorCode(errDiskNotFound),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range testCases {
|
||||||
|
actualErr := toAdminAPIErrCode(test.err)
|
||||||
|
if actualErr != test.expectedAPIErr {
|
||||||
|
t.Errorf("Test %d: Expected %v but received %v",
|
||||||
|
i+1, test.expectedAPIErr, actualErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteSetConfigResponse(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
status bool
|
||||||
|
errs []error
|
||||||
|
}{
|
||||||
|
// 1. all nodes returned success.
|
||||||
|
{
|
||||||
|
status: true,
|
||||||
|
errs: []error{nil, nil, nil, nil},
|
||||||
|
},
|
||||||
|
// 2. some nodes returned errors.
|
||||||
|
{
|
||||||
|
status: false,
|
||||||
|
errs: []error{errDiskNotFound, nil, errDiskAccessDenied, errFaultyDisk},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testPeers := []adminPeer{
|
||||||
|
adminPeer{
|
||||||
|
addr: "localhost:9001",
|
||||||
|
},
|
||||||
|
adminPeer{
|
||||||
|
addr: "localhost:9002",
|
||||||
|
},
|
||||||
|
adminPeer{
|
||||||
|
addr: "localhost:9003",
|
||||||
|
},
|
||||||
|
adminPeer{
|
||||||
|
addr: "localhost:9004",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testURL, err := url.Parse("dummy.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse a place-holder url")
|
||||||
|
}
|
||||||
|
|
||||||
|
var actualResult setConfigResult
|
||||||
|
for i, test := range testCases {
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
writeSetConfigResponse(rec, testPeers, test.errs, test.status, testURL)
|
||||||
|
resp := rec.Result()
|
||||||
|
jsonBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %d: Failed to read response %v", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(jsonBytes, &actualResult)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %d: Failed to unmarshal json %v", i+1, err)
|
||||||
|
}
|
||||||
|
if actualResult.Status != test.status {
|
||||||
|
t.Errorf("Test %d: Expected status %v but received %v", i+1, test.status, actualResult.Status)
|
||||||
|
}
|
||||||
|
for p, res := range actualResult.NodeResults {
|
||||||
|
if res.Name != testPeers[p].addr {
|
||||||
|
t.Errorf("Test %d: Expected node name %s but received %s", i+1, testPeers[p].addr, res.Name)
|
||||||
|
}
|
||||||
|
expectedErrStr := fmt.Sprintf("%v", test.errs[p])
|
||||||
|
if res.Err != expectedErrStr {
|
||||||
|
t.Errorf("Test %d: Expected error %s but received %s", i+1, expectedErrStr, res.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -67,4 +67,6 @@ func registerAdminRouter(mux *router.Router) {
|
|||||||
|
|
||||||
// Get config
|
// Get config
|
||||||
adminRouter.Methods("GET").Queries("config", "").Headers(minioAdminOpHeader, "get").HandlerFunc(adminAPI.GetConfigHandler)
|
adminRouter.Methods("GET").Queries("config", "").Headers(minioAdminOpHeader, "get").HandlerFunc(adminAPI.GetConfigHandler)
|
||||||
|
// Set Config
|
||||||
|
adminRouter.Methods("PUT").Queries("config", "").Headers(minioAdminOpHeader, "set").HandlerFunc(adminAPI.SetConfigHandler)
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,26 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Admin service names
|
||||||
|
serviceRestartRPC = "Admin.Restart"
|
||||||
|
listLocksRPC = "Admin.ListLocks"
|
||||||
|
reInitDisksRPC = "Admin.ReInitDisks"
|
||||||
|
uptimeRPC = "Admin.Uptime"
|
||||||
|
getConfigRPC = "Admin.GetConfig"
|
||||||
|
writeTmpConfigRPC = "Admin.WriteTmpConfig"
|
||||||
|
commitConfigRPC = "Admin.CommitConfig"
|
||||||
|
)
|
||||||
|
|
||||||
// localAdminClient - represents admin operation to be executed locally.
|
// localAdminClient - represents admin operation to be executed locally.
|
||||||
type localAdminClient struct {
|
type localAdminClient struct {
|
||||||
}
|
}
|
||||||
@ -45,6 +58,8 @@ type adminCmdRunner interface {
|
|||||||
ReInitDisks() error
|
ReInitDisks() error
|
||||||
Uptime() (time.Duration, error)
|
Uptime() (time.Duration, error)
|
||||||
GetConfig() ([]byte, error)
|
GetConfig() ([]byte, error)
|
||||||
|
WriteTmpConfig(tmpFileName string, configBytes []byte) error
|
||||||
|
CommitConfig(tmpFileName string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart - Sends a message over channel to the go-routine
|
// Restart - Sends a message over channel to the go-routine
|
||||||
@ -63,7 +78,7 @@ func (lc localAdminClient) ListLocks(bucket, prefix string, duration time.Durati
|
|||||||
func (rc remoteAdminClient) Restart() error {
|
func (rc remoteAdminClient) Restart() error {
|
||||||
args := AuthRPCArgs{}
|
args := AuthRPCArgs{}
|
||||||
reply := AuthRPCReply{}
|
reply := AuthRPCReply{}
|
||||||
return rc.Call("Admin.Restart", &args, &reply)
|
return rc.Call(serviceRestartRPC, &args, &reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListLocks - Sends list locks command to remote server via RPC.
|
// ListLocks - Sends list locks command to remote server via RPC.
|
||||||
@ -74,7 +89,7 @@ func (rc remoteAdminClient) ListLocks(bucket, prefix string, duration time.Durat
|
|||||||
duration: duration,
|
duration: duration,
|
||||||
}
|
}
|
||||||
var reply ListLocksReply
|
var reply ListLocksReply
|
||||||
if err := rc.Call("Admin.ListLocks", &listArgs, &reply); err != nil {
|
if err := rc.Call(listLocksRPC, &listArgs, &reply); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return reply.volLocks, nil
|
return reply.volLocks, nil
|
||||||
@ -91,7 +106,7 @@ func (lc localAdminClient) ReInitDisks() error {
|
|||||||
func (rc remoteAdminClient) ReInitDisks() error {
|
func (rc remoteAdminClient) ReInitDisks() error {
|
||||||
args := AuthRPCArgs{}
|
args := AuthRPCArgs{}
|
||||||
reply := AuthRPCReply{}
|
reply := AuthRPCReply{}
|
||||||
return rc.Call("Admin.ReInitDisks", &args, &reply)
|
return rc.Call(reInitDisksRPC, &args, &reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uptime - Returns the uptime of this server. Timestamp is taken
|
// Uptime - Returns the uptime of this server. Timestamp is taken
|
||||||
@ -108,7 +123,7 @@ func (lc localAdminClient) Uptime() (time.Duration, error) {
|
|||||||
func (rc remoteAdminClient) Uptime() (time.Duration, error) {
|
func (rc remoteAdminClient) Uptime() (time.Duration, error) {
|
||||||
args := AuthRPCArgs{}
|
args := AuthRPCArgs{}
|
||||||
reply := UptimeReply{}
|
reply := UptimeReply{}
|
||||||
err := rc.Call("Admin.Uptime", &args, &reply)
|
err := rc.Call(uptimeRPC, &args, &reply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Duration(0), err
|
return time.Duration(0), err
|
||||||
}
|
}
|
||||||
@ -129,12 +144,70 @@ func (lc localAdminClient) GetConfig() ([]byte, error) {
|
|||||||
func (rc remoteAdminClient) GetConfig() ([]byte, error) {
|
func (rc remoteAdminClient) GetConfig() ([]byte, error) {
|
||||||
args := AuthRPCArgs{}
|
args := AuthRPCArgs{}
|
||||||
reply := ConfigReply{}
|
reply := ConfigReply{}
|
||||||
if err := rc.Call("Admin.GetConfig", &args, &reply); err != nil {
|
if err := rc.Call(getConfigRPC, &args, &reply); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return reply.Config, nil
|
return reply.Config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteTmpConfig - writes config file content to a temporary file on
|
||||||
|
// the local server.
|
||||||
|
func (lc localAdminClient) WriteTmpConfig(tmpFileName string, configBytes []byte) error {
|
||||||
|
return writeTmpConfigCommon(tmpFileName, configBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTmpConfig - writes config file content to a temporary file on
|
||||||
|
// a remote node.
|
||||||
|
func (rc remoteAdminClient) WriteTmpConfig(tmpFileName string, configBytes []byte) error {
|
||||||
|
wArgs := WriteConfigArgs{
|
||||||
|
TmpFileName: tmpFileName,
|
||||||
|
Buf: configBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rc.Call(writeTmpConfigRPC, &wArgs, &WriteConfigReply{})
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Failed to write temporary config file.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitConfig - Move the new config in tmpFileName onto config.json
|
||||||
|
// on a local node.
|
||||||
|
func (lc localAdminClient) CommitConfig(tmpFileName string) error {
|
||||||
|
configDir, err := getConfigPath()
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Failed to get config directory path.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configFilePath := filepath.Join(configDir, globalMinioConfigFile)
|
||||||
|
err = os.Rename(filepath.Join(configDir, tmpFileName), configFilePath)
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Failed to rename to config.json")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitConfig - Move the new config in tmpFileName onto config.json
|
||||||
|
// on a remote node.
|
||||||
|
func (rc remoteAdminClient) CommitConfig(tmpFileName string) error {
|
||||||
|
cArgs := CommitConfigArgs{
|
||||||
|
FileName: tmpFileName,
|
||||||
|
}
|
||||||
|
cReply := CommitConfigReply{}
|
||||||
|
err := rc.Call(commitConfigRPC, &cArgs, &cReply)
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Failed to rename config file.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -489,3 +562,55 @@ func getValidServerConfig(serverConfigs []serverConfigV13, errs []error) (server
|
|||||||
|
|
||||||
return configJSON, nil
|
return configJSON, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write config contents into a temporary file on all nodes.
|
||||||
|
func writeTmpConfigPeers(peers adminPeers, tmpFileName string, configBytes []byte) []error {
|
||||||
|
// For a single-node minio server setup.
|
||||||
|
if !globalIsDistXL {
|
||||||
|
err := peers[0].cmdRunner.WriteTmpConfig(tmpFileName, configBytes)
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, len(peers))
|
||||||
|
|
||||||
|
// Write config into temporary file on all nodes.
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for i, peer := range peers {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int, peer adminPeer) {
|
||||||
|
defer wg.Done()
|
||||||
|
errs[idx] = peer.cmdRunner.WriteTmpConfig(tmpFileName, configBytes)
|
||||||
|
}(i, peer)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Return bytes written and errors (if any) during writing
|
||||||
|
// temporary config file.
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move config contents from the given temporary file onto config.json
|
||||||
|
// on all nodes.
|
||||||
|
func commitConfigPeers(peers adminPeers, tmpFileName string) []error {
|
||||||
|
// For a single-node minio server setup.
|
||||||
|
if !globalIsDistXL {
|
||||||
|
return []error{peers[0].cmdRunner.CommitConfig(tmpFileName)}
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, len(peers))
|
||||||
|
|
||||||
|
// Rename temporary config file into configDir/config.json on
|
||||||
|
// all nodes.
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for i, peer := range peers {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int, peer adminPeer) {
|
||||||
|
defer wg.Done()
|
||||||
|
errs[idx] = peer.cmdRunner.CommitConfig(tmpFileName)
|
||||||
|
}(i, peer)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Return errors (if any) received during rename.
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
@ -19,7 +19,10 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
"net/rpc"
|
"net/rpc"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
router "github.com/gorilla/mux"
|
router "github.com/gorilla/mux"
|
||||||
@ -158,6 +161,75 @@ func (s *adminCmd) GetConfig(args *AuthRPCArgs, reply *ConfigReply) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteConfigArgs - wraps the bytes to be written and temporary file name.
|
||||||
|
type WriteConfigArgs struct {
|
||||||
|
AuthRPCArgs
|
||||||
|
TmpFileName string
|
||||||
|
Buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteConfigReply - wraps the result of a writing config into a temporary file.
|
||||||
|
// the remote node.
|
||||||
|
type WriteConfigReply struct {
|
||||||
|
AuthRPCReply
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTmpConfigCommon(tmpFileName string, configBytes []byte) error {
|
||||||
|
configDir, err := getConfigPath()
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Failed to get config path")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filepath.Join(configDir, tmpFileName), configBytes, 0666)
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Failed to write to temporary config file.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTmpConfig - writes the supplied config contents onto the
|
||||||
|
// supplied temporary file.
|
||||||
|
func (s *adminCmd) WriteTmpConfig(wArgs *WriteConfigArgs, wReply *WriteConfigReply) error {
|
||||||
|
if err := wArgs.IsAuthenticated(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeTmpConfigCommon(wArgs.TmpFileName, wArgs.Buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitConfigArgs - wraps the config file name that needs to be
|
||||||
|
// committed into config.json on this node.
|
||||||
|
type CommitConfigArgs struct {
|
||||||
|
AuthRPCArgs
|
||||||
|
FileName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitConfigReply - represents response to commit of config file on
|
||||||
|
// this node.
|
||||||
|
type CommitConfigReply struct {
|
||||||
|
AuthRPCReply
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitConfig - Renames the temporary file into config.json on this node.
|
||||||
|
func (s *adminCmd) CommitConfig(cArgs *CommitConfigArgs, cReply *CommitConfigReply) error {
|
||||||
|
configDir, err := getConfigPath()
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Failed to get config path.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configFilePath := filepath.Join(configDir, globalMinioConfigFile)
|
||||||
|
err = os.Rename(filepath.Join(configDir, cArgs.FileName), configFilePath)
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Failed to rename config file.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@ -144,6 +144,7 @@ func TestReInitDisks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGetConfig - Test for GetConfig admin RPC.
|
||||||
func TestGetConfig(t *testing.T) {
|
func TestGetConfig(t *testing.T) {
|
||||||
// Reset global variables to start afresh.
|
// Reset global variables to start afresh.
|
||||||
resetTestGlobals()
|
resetTestGlobals()
|
||||||
@ -185,3 +186,63 @@ func TestGetConfig(t *testing.T) {
|
|||||||
t.Errorf("Expected json unmarshal to pass but failed with %v", err)
|
t.Errorf("Expected json unmarshal to pass but failed with %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestWriteAndCommitConfig - test for WriteTmpConfig and CommitConfig
|
||||||
|
// RPC handler.
|
||||||
|
func TestWriteAndCommitConfig(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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write temporary config.
|
||||||
|
buf := []byte("hello")
|
||||||
|
tmpFileName := mustGetUUID()
|
||||||
|
wArgs := WriteConfigArgs{
|
||||||
|
AuthRPCArgs: AuthRPCArgs{
|
||||||
|
AuthToken: reply.AuthToken,
|
||||||
|
},
|
||||||
|
TmpFileName: tmpFileName,
|
||||||
|
Buf: buf,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = adminServer.WriteTmpConfig(&wArgs, &WriteConfigReply{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write temporary config %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected to succeed but failed %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cArgs := CommitConfigArgs{
|
||||||
|
AuthRPCArgs: AuthRPCArgs{
|
||||||
|
AuthToken: reply.AuthToken,
|
||||||
|
},
|
||||||
|
FileName: tmpFileName,
|
||||||
|
}
|
||||||
|
cReply := CommitConfigReply{}
|
||||||
|
|
||||||
|
err = adminServer.CommitConfig(&cArgs, &cReply)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to commit config file %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -144,6 +144,7 @@ const (
|
|||||||
|
|
||||||
ErrAdminInvalidAccessKey
|
ErrAdminInvalidAccessKey
|
||||||
ErrAdminInvalidSecretKey
|
ErrAdminInvalidSecretKey
|
||||||
|
ErrAdminConfigNoQuorum
|
||||||
)
|
)
|
||||||
|
|
||||||
// error code to APIError structure, these fields carry respective
|
// error code to APIError structure, these fields carry respective
|
||||||
@ -593,6 +594,11 @@ var errorCodeResponse = map[APIErrorCode]APIError{
|
|||||||
Description: "The secret key is invalid.",
|
Description: "The secret key is invalid.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminConfigNoQuorum: {
|
||||||
|
Code: "XMinioAdminConfigNoQuorum",
|
||||||
|
Description: "Configuration update failed because server quorum was not met",
|
||||||
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
|
},
|
||||||
|
|
||||||
// Add your error structure here.
|
// Add your error structure here.
|
||||||
}
|
}
|
||||||
|
@ -351,7 +351,7 @@ func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A put method on path "/" doesn't make sense, ignore it.
|
// A put method on path "/" doesn't make sense, ignore it.
|
||||||
if r.Method == httpPUT && r.URL.Path == "/" {
|
if r.Method == httpPUT && r.URL.Path == "/" && r.Header.Get(minioAdminOpHeader) == "" {
|
||||||
writeErrorResponse(w, ErrNotImplemented, r.URL)
|
writeErrorResponse(w, ErrNotImplemented, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ func main() {
|
|||||||
| Service operations|LockInfo operations|Healing operations|Config operations| Misc |
|
| Service operations|LockInfo operations|Healing operations|Config operations| Misc |
|
||||||
|:---|:---|:---|:---|:---|
|
|:---|:---|:---|:---|:---|
|
||||||
|[`ServiceStatus`](#ServiceStatus)| [`ListLocks`](#ListLocks)| [`ListObjectsHeal`](#ListObjectsHeal)|[`GetConfig`](#GetConfig)| [`SetCredentials`](#SetCredentials)|
|
|[`ServiceStatus`](#ServiceStatus)| [`ListLocks`](#ListLocks)| [`ListObjectsHeal`](#ListObjectsHeal)|[`GetConfig`](#GetConfig)| [`SetCredentials`](#SetCredentials)|
|
||||||
|[`ServiceRestart`](#ServiceRestart)| [`ClearLocks`](#ClearLocks)| [`ListBucketsHeal`](#ListBucketsHeal)|||
|
|[`ServiceRestart`](#ServiceRestart)| [`ClearLocks`](#ClearLocks)| [`ListBucketsHeal`](#ListBucketsHeal)|[`SetConfig`](#SetConfig)||
|
||||||
| | |[`HealBucket`](#HealBucket) |||
|
| | |[`HealBucket`](#HealBucket) |||
|
||||||
| | |[`HealObject`](#HealObject)|||
|
| | |[`HealObject`](#HealObject)|||
|
||||||
| | |[`HealFormat`](#HealFormat)|||
|
| | |[`HealFormat`](#HealFormat)|||
|
||||||
@ -306,7 +306,7 @@ __Example__
|
|||||||
|
|
||||||
<a name="SetCredentials"></a>
|
<a name="SetCredentials"></a>
|
||||||
|
|
||||||
### SetCredentials() error
|
### SetCredentials() error
|
||||||
Set new credentials of a Minio setup.
|
Set new credentials of a Minio setup.
|
||||||
|
|
||||||
__Example__
|
__Example__
|
||||||
@ -320,4 +320,35 @@ __Example__
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<a name="SetConfig"></a>
|
||||||
|
### SetConfig(config io.Reader) (SetConfigResult, error)
|
||||||
|
Set config.json of a minio setup and restart setup for configuration
|
||||||
|
change to take effect.
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
|`st.Status` | _bool_ | true if set-config succeeded, false otherwise. |
|
||||||
|
|`st.NodeSummary.Name` | _string_ | Network address of the node. |
|
||||||
|
|`st.NodeSummary.Err` | _string_ | String representation of the error (if any) on the node.|
|
||||||
|
|
||||||
|
|
||||||
|
__Example__
|
||||||
|
|
||||||
|
``` go
|
||||||
|
config := bytes.NewReader([]byte(`config.json contents go here`))
|
||||||
|
result, err := madmClnt.SetConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed due to: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := json.NewEncoder(&buf)
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
enc.SetIndent("", "\t")
|
||||||
|
err = enc.Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
log.Println("SetConfig: ", string(buf.Bytes()))
|
||||||
|
```
|
||||||
|
@ -18,15 +18,36 @@
|
|||||||
package madmin
|
package madmin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
configQueryParam = "config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeSummary - represents the result of an operation part of
|
||||||
|
// set-config on a node.
|
||||||
|
type NodeSummary struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Err string `json:"err"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfigResult - represents detailed results of a set-config
|
||||||
|
// operation.
|
||||||
|
type SetConfigResult struct {
|
||||||
|
NodeResults []NodeSummary `json:"nodeResults"`
|
||||||
|
Status bool `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfig - returns the config.json of a minio setup.
|
// GetConfig - returns the config.json of a minio setup.
|
||||||
func (adm *AdminClient) GetConfig() ([]byte, error) {
|
func (adm *AdminClient) GetConfig() ([]byte, error) {
|
||||||
queryVal := make(url.Values)
|
queryVal := make(url.Values)
|
||||||
queryVal.Set("config", "")
|
queryVal.Set(configQueryParam, "")
|
||||||
|
|
||||||
hdrs := make(http.Header)
|
hdrs := make(http.Header)
|
||||||
hdrs.Set(minioAdminOpHeader, "get")
|
hdrs.Set(minioAdminOpHeader, "get")
|
||||||
@ -36,7 +57,7 @@ func (adm *AdminClient) GetConfig() ([]byte, error) {
|
|||||||
customHeaders: hdrs,
|
customHeaders: hdrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute GET on /?lock to list locks.
|
// Execute GET on /?config to get config of a setup.
|
||||||
resp, err := adm.executeMethod("GET", reqData)
|
resp, err := adm.executeMethod("GET", reqData)
|
||||||
|
|
||||||
defer closeResponse(resp)
|
defer closeResponse(resp)
|
||||||
@ -44,6 +65,59 @@ func (adm *AdminClient) GetConfig() ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, httpRespToErrorResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
// Return the JSON marshalled bytes to user.
|
// Return the JSON marshalled bytes to user.
|
||||||
return ioutil.ReadAll(resp.Body)
|
return ioutil.ReadAll(resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetConfig - set config supplied as config.json for the setup.
|
||||||
|
func (adm *AdminClient) SetConfig(config io.Reader) (SetConfigResult, error) {
|
||||||
|
queryVal := url.Values{}
|
||||||
|
queryVal.Set(configQueryParam, "")
|
||||||
|
|
||||||
|
// Set x-minio-operation to set.
|
||||||
|
hdrs := make(http.Header)
|
||||||
|
hdrs.Set(minioAdminOpHeader, "set")
|
||||||
|
|
||||||
|
// Read config bytes to calculate MD5, SHA256 and content length.
|
||||||
|
configBytes, err := ioutil.ReadAll(config)
|
||||||
|
if err != nil {
|
||||||
|
return SetConfigResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := requestData{
|
||||||
|
queryValues: queryVal,
|
||||||
|
customHeaders: hdrs,
|
||||||
|
contentBody: bytes.NewReader(configBytes),
|
||||||
|
contentMD5Bytes: sumMD5(configBytes),
|
||||||
|
contentSHA256Bytes: sum256(configBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute PUT on /?config to set config.
|
||||||
|
resp, err := adm.executeMethod("PUT", reqData)
|
||||||
|
|
||||||
|
defer closeResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return SetConfigResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return SetConfigResult{}, httpRespToErrorResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result SetConfigResult
|
||||||
|
jsonBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return SetConfigResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(jsonBytes, &result)
|
||||||
|
if err != nil {
|
||||||
|
return SetConfigResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
156
pkg/madmin/examples/set-config.go
Normal file
156
pkg/madmin/examples/set-config.go
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// +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 (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configJSON = []byte(`{
|
||||||
|
"version": "13",
|
||||||
|
"credential": {
|
||||||
|
"accessKey": "minio",
|
||||||
|
"secretKey": "minio123"
|
||||||
|
},
|
||||||
|
"region": "us-west-1",
|
||||||
|
"logger": {
|
||||||
|
"console": {
|
||||||
|
"enable": true,
|
||||||
|
"level": "fatal"
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"enable": false,
|
||||||
|
"fileName": "",
|
||||||
|
"level": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notify": {
|
||||||
|
"amqp": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"url": "",
|
||||||
|
"exchange": "",
|
||||||
|
"routingKey": "",
|
||||||
|
"exchangeType": "",
|
||||||
|
"mandatory": false,
|
||||||
|
"immediate": false,
|
||||||
|
"durable": false,
|
||||||
|
"internal": false,
|
||||||
|
"noWait": false,
|
||||||
|
"autoDeleted": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nats": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"address": "",
|
||||||
|
"subject": "",
|
||||||
|
"username": "",
|
||||||
|
"password": "",
|
||||||
|
"token": "",
|
||||||
|
"secure": false,
|
||||||
|
"pingInterval": 0,
|
||||||
|
"streaming": {
|
||||||
|
"enable": false,
|
||||||
|
"clusterID": "",
|
||||||
|
"clientID": "",
|
||||||
|
"async": false,
|
||||||
|
"maxPubAcksInflight": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"elasticsearch": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"url": "",
|
||||||
|
"index": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redis": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"address": "",
|
||||||
|
"password": "",
|
||||||
|
"key": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postgresql": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"connectionString": "",
|
||||||
|
"table": "",
|
||||||
|
"host": "",
|
||||||
|
"port": "",
|
||||||
|
"user": "",
|
||||||
|
"password": "",
|
||||||
|
"database": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"kafka": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"brokers": null,
|
||||||
|
"topic": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhook": {
|
||||||
|
"1": {
|
||||||
|
"enable": false,
|
||||||
|
"endpoint": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := madmClnt.SetConfig(bytes.NewReader(configJSON))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := json.NewEncoder(&buf)
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
enc.SetIndent("", "\t")
|
||||||
|
err = enc.Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("SetConfig: ", string(buf.Bytes()))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user