|
|
@ -21,6 +21,7 @@ import ( |
|
|
|
"context" |
|
|
|
"context" |
|
|
|
"encoding/base64" |
|
|
|
"encoding/base64" |
|
|
|
"encoding/json" |
|
|
|
"encoding/json" |
|
|
|
|
|
|
|
"errors" |
|
|
|
"fmt" |
|
|
|
"fmt" |
|
|
|
"io" |
|
|
|
"io" |
|
|
|
"io/ioutil" |
|
|
|
"io/ioutil" |
|
|
@ -476,7 +477,6 @@ func newLockEntry(l lockRequesterInfo, resource, server string) *madmin.LockEntr |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func topLockEntries(peerLocks []*PeerLocks) madmin.LockEntries { |
|
|
|
func topLockEntries(peerLocks []*PeerLocks) madmin.LockEntries { |
|
|
|
const listCount int = 10 |
|
|
|
|
|
|
|
entryMap := make(map[string]*madmin.LockEntry) |
|
|
|
entryMap := make(map[string]*madmin.LockEntry) |
|
|
|
for _, peerLock := range peerLocks { |
|
|
|
for _, peerLock := range peerLocks { |
|
|
|
if peerLock == nil { |
|
|
|
if peerLock == nil { |
|
|
@ -497,6 +497,7 @@ func topLockEntries(peerLocks []*PeerLocks) madmin.LockEntries { |
|
|
|
lockEntries = append(lockEntries, *v) |
|
|
|
lockEntries = append(lockEntries, *v) |
|
|
|
} |
|
|
|
} |
|
|
|
sort.Sort(lockEntries) |
|
|
|
sort.Sort(lockEntries) |
|
|
|
|
|
|
|
const listCount int = 10 |
|
|
|
if len(lockEntries) > listCount { |
|
|
|
if len(lockEntries) > listCount { |
|
|
|
lockEntries = lockEntries[:listCount] |
|
|
|
lockEntries = lockEntries[:listCount] |
|
|
|
} |
|
|
|
} |
|
|
@ -654,45 +655,60 @@ func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *htt |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// extractHealInitParams - Validates params for heal init API.
|
|
|
|
type healInitParams struct { |
|
|
|
func extractHealInitParams(r *http.Request) (bucket, objPrefix string, |
|
|
|
bucket, objPrefix string |
|
|
|
hs madmin.HealOpts, clientToken string, forceStart bool, forceStop bool, |
|
|
|
hs madmin.HealOpts |
|
|
|
err APIErrorCode) { |
|
|
|
clientToken string |
|
|
|
|
|
|
|
forceStart, forceStop bool |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
vars := mux.Vars(r) |
|
|
|
// extractHealInitParams - Validates params for heal init API.
|
|
|
|
bucket = vars[string(mgmtBucket)] |
|
|
|
func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reader) (hip healInitParams, err APIErrorCode) { |
|
|
|
objPrefix = vars[string(mgmtPrefix)] |
|
|
|
hip.bucket = vars[string(mgmtBucket)] |
|
|
|
|
|
|
|
hip.objPrefix = vars[string(mgmtPrefix)] |
|
|
|
|
|
|
|
|
|
|
|
if bucket == "" { |
|
|
|
if hip.bucket == "" { |
|
|
|
if objPrefix != "" { |
|
|
|
if hip.objPrefix != "" { |
|
|
|
// Bucket is required if object-prefix is given
|
|
|
|
// Bucket is required if object-prefix is given
|
|
|
|
err = ErrHealMissingBucket |
|
|
|
err = ErrHealMissingBucket |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
} else if isReservedOrInvalidBucket(bucket, false) { |
|
|
|
} else if isReservedOrInvalidBucket(hip.bucket, false) { |
|
|
|
err = ErrInvalidBucketName |
|
|
|
err = ErrInvalidBucketName |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// empty prefix is valid.
|
|
|
|
// empty prefix is valid.
|
|
|
|
if !IsValidObjectPrefix(objPrefix) { |
|
|
|
if !IsValidObjectPrefix(hip.objPrefix) { |
|
|
|
err = ErrInvalidObjectName |
|
|
|
err = ErrInvalidObjectName |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
qParms := r.URL.Query() |
|
|
|
|
|
|
|
if len(qParms[string(mgmtClientToken)]) > 0 { |
|
|
|
if len(qParms[string(mgmtClientToken)]) > 0 { |
|
|
|
clientToken = qParms[string(mgmtClientToken)][0] |
|
|
|
hip.clientToken = qParms[string(mgmtClientToken)][0] |
|
|
|
} |
|
|
|
} |
|
|
|
if _, ok := qParms[string(mgmtForceStart)]; ok { |
|
|
|
if _, ok := qParms[string(mgmtForceStart)]; ok { |
|
|
|
forceStart = true |
|
|
|
hip.forceStart = true |
|
|
|
} |
|
|
|
} |
|
|
|
if _, ok := qParms[string(mgmtForceStop)]; ok { |
|
|
|
if _, ok := qParms[string(mgmtForceStop)]; ok { |
|
|
|
forceStop = true |
|
|
|
hip.forceStop = true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Invalid request conditions:
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Cannot have both forceStart and forceStop in the same
|
|
|
|
|
|
|
|
// request; If clientToken is provided, request can only be
|
|
|
|
|
|
|
|
// to continue receiving logs, so it cannot be start or
|
|
|
|
|
|
|
|
// stop;
|
|
|
|
|
|
|
|
if (hip.forceStart && hip.forceStop) || |
|
|
|
|
|
|
|
(hip.clientToken != "" && (hip.forceStart || hip.forceStop)) { |
|
|
|
|
|
|
|
err = ErrInvalidRequest |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ignore body if clientToken is provided
|
|
|
|
// ignore body if clientToken is provided
|
|
|
|
if clientToken == "" { |
|
|
|
if hip.clientToken == "" { |
|
|
|
jerr := json.NewDecoder(r.Body).Decode(&hs) |
|
|
|
jerr := json.NewDecoder(r).Decode(&hip.hs) |
|
|
|
if jerr != nil { |
|
|
|
if jerr != nil { |
|
|
|
logger.LogIf(context.Background(), jerr) |
|
|
|
logger.LogIf(context.Background(), jerr) |
|
|
|
err = ErrRequestBodyParse |
|
|
|
err = ErrRequestBodyParse |
|
|
@ -731,7 +747,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bucket, objPrefix, hs, clientToken, forceStart, forceStop, errCode := extractHealInitParams(r) |
|
|
|
hip, errCode := extractHealInitParams(mux.Vars(r), r.URL.Query(), r.Body) |
|
|
|
if errCode != ErrNone { |
|
|
|
if errCode != ErrNone { |
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) |
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) |
|
|
|
return |
|
|
|
return |
|
|
@ -805,8 +821,8 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
info := objectAPI.StorageInfo(ctx) |
|
|
|
info := objectAPI.StorageInfo(ctx) |
|
|
|
numDisks := info.Backend.OfflineDisks + info.Backend.OnlineDisks |
|
|
|
numDisks := info.Backend.OfflineDisks + info.Backend.OnlineDisks |
|
|
|
|
|
|
|
|
|
|
|
healPath := pathJoin(bucket, objPrefix) |
|
|
|
healPath := pathJoin(hip.bucket, hip.objPrefix) |
|
|
|
if clientToken == "" && !forceStart && !forceStop { |
|
|
|
if hip.clientToken == "" && !hip.forceStart && !hip.forceStop { |
|
|
|
nh, exists := globalAllHealState.getHealSequence(healPath) |
|
|
|
nh, exists := globalAllHealState.getHealSequence(healPath) |
|
|
|
if exists && !nh.hasEnded() && len(nh.currentStatus.Items) > 0 { |
|
|
|
if exists && !nh.hasEnded() && len(nh.currentStatus.Items) > 0 { |
|
|
|
b, err := json.Marshal(madmin.HealStartSuccess{ |
|
|
|
b, err := json.Marshal(madmin.HealStartSuccess{ |
|
|
@ -825,11 +841,11 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if clientToken != "" && !forceStart && !forceStop { |
|
|
|
if hip.clientToken != "" && !hip.forceStart && !hip.forceStop { |
|
|
|
// Since clientToken is given, fetch heal status from running
|
|
|
|
// Since clientToken is given, fetch heal status from running
|
|
|
|
// heal sequence.
|
|
|
|
// heal sequence.
|
|
|
|
respBytes, errCode := globalAllHealState.PopHealStatusJSON( |
|
|
|
respBytes, errCode := globalAllHealState.PopHealStatusJSON( |
|
|
|
healPath, clientToken) |
|
|
|
healPath, hip.clientToken) |
|
|
|
if errCode != ErrNone { |
|
|
|
if errCode != ErrNone { |
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) |
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) |
|
|
|
} else { |
|
|
|
} else { |
|
|
@ -840,14 +856,14 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
|
|
|
|
|
|
|
respCh := make(chan healResp) |
|
|
|
respCh := make(chan healResp) |
|
|
|
switch { |
|
|
|
switch { |
|
|
|
case forceStop: |
|
|
|
case hip.forceStop: |
|
|
|
go func() { |
|
|
|
go func() { |
|
|
|
respBytes, apiErr := globalAllHealState.stopHealSequence(healPath) |
|
|
|
respBytes, apiErr := globalAllHealState.stopHealSequence(healPath) |
|
|
|
hr := healResp{respBytes: respBytes, apiErr: apiErr} |
|
|
|
hr := healResp{respBytes: respBytes, apiErr: apiErr} |
|
|
|
respCh <- hr |
|
|
|
respCh <- hr |
|
|
|
}() |
|
|
|
}() |
|
|
|
case clientToken == "": |
|
|
|
case hip.clientToken == "": |
|
|
|
nh := newHealSequence(bucket, objPrefix, handlers.GetSourceIP(r), numDisks, hs, forceStart) |
|
|
|
nh := newHealSequence(hip.bucket, hip.objPrefix, handlers.GetSourceIP(r), numDisks, hip.hs, hip.forceStart) |
|
|
|
go func() { |
|
|
|
go func() { |
|
|
|
respBytes, apiErr, errMsg := globalAllHealState.LaunchNewHealSequence(nh) |
|
|
|
respBytes, apiErr, errMsg := globalAllHealState.LaunchNewHealSequence(nh) |
|
|
|
hr := healResp{respBytes, apiErr, errMsg} |
|
|
|
hr := healResp{respBytes, apiErr, errMsg} |
|
|
@ -1609,7 +1625,10 @@ func convertValueType(elem []byte, jsonType gjson.Type) (interface{}, error) { |
|
|
|
case gjson.False, gjson.True: |
|
|
|
case gjson.False, gjson.True: |
|
|
|
return strconv.ParseBool(str) |
|
|
|
return strconv.ParseBool(str) |
|
|
|
case gjson.JSON: |
|
|
|
case gjson.JSON: |
|
|
|
return gjson.Parse(str).Value(), nil |
|
|
|
if gjson.Valid(str) { |
|
|
|
|
|
|
|
return gjson.Parse(str).Value(), nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return nil, errors.New("invalid json") |
|
|
|
case gjson.String: |
|
|
|
case gjson.String: |
|
|
|
return str, nil |
|
|
|
return str, nil |
|
|
|
case gjson.Number: |
|
|
|
case gjson.Number: |
|
|
|