2021-04-19 17:30:42 +00:00
|
|
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
|
|
//
|
|
|
|
// This file is part of MinIO Object Storage stack
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2023-02-27 17:26:26 +00:00
|
|
|
"strconv"
|
2021-04-19 17:30:42 +00:00
|
|
|
|
2021-05-11 09:02:32 +00:00
|
|
|
jsoniter "github.com/json-iterator/go"
|
2022-12-06 21:46:50 +00:00
|
|
|
"github.com/minio/madmin-go/v2"
|
2021-10-24 01:38:33 +00:00
|
|
|
"github.com/minio/minio/internal/config/storageclass"
|
2021-06-01 21:59:40 +00:00
|
|
|
"github.com/minio/minio/internal/logger"
|
2023-01-23 11:12:47 +00:00
|
|
|
"github.com/minio/mux"
|
2021-05-30 04:16:42 +00:00
|
|
|
iampolicy "github.com/minio/pkg/iam/policy"
|
2021-04-19 17:30:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// error returned when remote tier already exists
|
|
|
|
errTierAlreadyExists = AdminError{
|
|
|
|
Code: "XMinioAdminTierAlreadyExists",
|
|
|
|
Message: "Specified remote tier already exists",
|
|
|
|
StatusCode: http.StatusConflict,
|
|
|
|
}
|
|
|
|
// error returned when remote tier is not found
|
|
|
|
errTierNotFound = AdminError{
|
|
|
|
Code: "XMinioAdminTierNotFound",
|
|
|
|
Message: "Specified remote tier was not found",
|
|
|
|
StatusCode: http.StatusNotFound,
|
|
|
|
}
|
|
|
|
// error returned when remote tier name is not in uppercase
|
|
|
|
errTierNameNotUppercase = AdminError{
|
|
|
|
Code: "XMinioAdminTierNameNotUpperCase",
|
|
|
|
Message: "Tier name must be in uppercase",
|
|
|
|
StatusCode: http.StatusBadRequest,
|
|
|
|
}
|
|
|
|
// error returned when remote tier bucket is not found
|
|
|
|
errTierBucketNotFound = AdminError{
|
|
|
|
Code: "XMinioAdminTierBucketNotFound",
|
|
|
|
Message: "Remote tier bucket not found",
|
|
|
|
StatusCode: http.StatusBadRequest,
|
|
|
|
}
|
|
|
|
// error returned when remote tier credentials are invalid.
|
|
|
|
errTierInvalidCredentials = AdminError{
|
|
|
|
Code: "XMinioAdminTierInvalidCredentials",
|
|
|
|
Message: "Invalid remote tier credentials",
|
|
|
|
StatusCode: http.StatusBadRequest,
|
|
|
|
}
|
2021-10-24 01:38:33 +00:00
|
|
|
// error returned when reserved internal names are used.
|
|
|
|
errTierReservedName = AdminError{
|
|
|
|
Code: "XMinioAdminTierReserved",
|
|
|
|
Message: "Cannot use reserved tier name",
|
|
|
|
StatusCode: http.StatusBadRequest,
|
|
|
|
}
|
2021-04-19 17:30:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func (api adminAPIHandlers) AddTierHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "AddTier")
|
|
|
|
|
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
|
|
|
2021-08-10 01:14:38 +00:00
|
|
|
objAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SetTierAction)
|
2021-04-19 17:30:42 +00:00
|
|
|
if objAPI == nil || globalNotificationSys == nil || globalTierConfigMgr == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
password := cred.SecretKey
|
|
|
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var cfg madmin.TierConfig
|
2022-01-02 17:15:06 +00:00
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
2021-04-19 17:30:42 +00:00
|
|
|
if err := json.Unmarshal(reqBytes, &cfg); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-27 17:26:26 +00:00
|
|
|
var ignoreInUse bool
|
|
|
|
if forceStr := r.Form.Get("force"); forceStr != "" {
|
|
|
|
ignoreInUse, _ = strconv.ParseBool(forceStr)
|
|
|
|
}
|
|
|
|
|
2021-10-24 01:38:33 +00:00
|
|
|
// Disallow remote tiers with internal storage class names
|
|
|
|
switch cfg.Name {
|
|
|
|
case storageclass.STANDARD, storageclass.RRS:
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errTierReservedName), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2022-12-08 19:18:07 +00:00
|
|
|
|
2021-04-19 17:30:42 +00:00
|
|
|
// Refresh from the disk in case we had missed notifications about edits from peers.
|
|
|
|
if err := globalTierConfigMgr.Reload(ctx, objAPI); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-27 17:26:26 +00:00
|
|
|
err = globalTierConfigMgr.Add(ctx, cfg, ignoreInUse)
|
2021-04-19 17:30:42 +00:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = globalTierConfigMgr.Save(ctx, objAPI)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
globalNotificationSys.LoadTransitionTierConfig(ctx)
|
|
|
|
|
|
|
|
writeSuccessNoContent(w)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api adminAPIHandlers) ListTierHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "ListTier")
|
|
|
|
|
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
|
|
|
2021-08-10 01:14:38 +00:00
|
|
|
objAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListTierAction)
|
2021-04-19 17:30:42 +00:00
|
|
|
if objAPI == nil || globalNotificationSys == nil || globalTierConfigMgr == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tiers := globalTierConfigMgr.ListTiers()
|
|
|
|
data, err := json.Marshal(tiers)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
writeSuccessResponseJSON(w, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api adminAPIHandlers) EditTierHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "EditTier")
|
|
|
|
|
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
|
|
|
2021-08-10 01:14:38 +00:00
|
|
|
objAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SetTierAction)
|
2021-04-19 17:30:42 +00:00
|
|
|
if objAPI == nil || globalNotificationSys == nil || globalTierConfigMgr == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
scName := vars["tier"]
|
|
|
|
|
|
|
|
password := cred.SecretKey
|
|
|
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var creds madmin.TierCreds
|
2022-01-02 17:15:06 +00:00
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
2021-04-19 17:30:42 +00:00
|
|
|
if err := json.Unmarshal(reqBytes, &creds); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Refresh from the disk in case we had missed notifications about edits from peers.
|
|
|
|
if err := globalTierConfigMgr.Reload(ctx, objAPI); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := globalTierConfigMgr.Edit(ctx, scName, creds); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := globalTierConfigMgr.Save(ctx, objAPI); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
globalNotificationSys.LoadTransitionTierConfig(ctx)
|
|
|
|
|
2022-02-23 21:34:25 +00:00
|
|
|
writeSuccessNoContent(w)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api adminAPIHandlers) RemoveTierHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "RemoveTier")
|
|
|
|
|
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
|
|
|
|
|
|
objAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetTierAction)
|
|
|
|
if objAPI == nil || globalNotificationSys == nil || globalTierConfigMgr == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
tier := vars["tier"]
|
|
|
|
if err := globalTierConfigMgr.Reload(ctx, objAPI); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := globalTierConfigMgr.Remove(ctx, tier); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := globalTierConfigMgr.Save(ctx, objAPI); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
globalNotificationSys.LoadTransitionTierConfig(ctx)
|
|
|
|
|
|
|
|
writeSuccessNoContent(w)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api adminAPIHandlers) VerifyTierHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "VerifyTier")
|
|
|
|
|
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
|
|
|
|
|
|
objAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListTierAction)
|
|
|
|
if objAPI == nil || globalNotificationSys == nil || globalTierConfigMgr == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
tier := vars["tier"]
|
|
|
|
if err := globalTierConfigMgr.Verify(ctx, tier); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-19 17:30:42 +00:00
|
|
|
writeSuccessNoContent(w)
|
|
|
|
}
|
2021-10-24 01:38:33 +00:00
|
|
|
|
|
|
|
func (api adminAPIHandlers) TierStatsHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "TierStats")
|
|
|
|
|
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
|
|
|
|
|
|
objAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListTierAction)
|
|
|
|
if objAPI == nil || globalNotificationSys == nil || globalTierConfigMgr == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
dui, err := loadDataUsageFromBackend(ctx, objAPI)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-26 22:33:10 +00:00
|
|
|
tierStats := dui.tierStats()
|
|
|
|
dailyStats := globalNotificationSys.GetLastDayTierStats(ctx)
|
|
|
|
tierStats = dailyStats.addToTierInfo(tierStats)
|
|
|
|
|
|
|
|
data, err := json.Marshal(tierStats)
|
2021-10-24 01:38:33 +00:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
writeSuccessResponseJSON(w, data)
|
|
|
|
}
|