| 
									
										
										
										
											2021-04-20 01:30:42 +08: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-28 01:26:26 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-11 17:02:32 +08:00
										 |  |  | 	jsoniter "github.com/json-iterator/go" | 
					
						
							| 
									
										
										
										
											2023-06-20 08:53:08 +08:00
										 |  |  | 	"github.com/minio/madmin-go/v3" | 
					
						
							| 
									
										
										
										
											2021-10-24 09:38:33 +08:00
										 |  |  | 	"github.com/minio/minio/internal/config/storageclass" | 
					
						
							| 
									
										
										
										
											2021-06-02 05:59:40 +08:00
										 |  |  | 	"github.com/minio/minio/internal/logger" | 
					
						
							| 
									
										
										
										
											2023-01-23 19:12:47 +08:00
										 |  |  | 	"github.com/minio/mux" | 
					
						
							| 
									
										
										
										
											2021-05-30 12:16:42 +08:00
										 |  |  | 	iampolicy "github.com/minio/pkg/iam/policy" | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08: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 09:38:33 +08:00
										 |  |  | 	// error returned when reserved internal names are used.
 | 
					
						
							|  |  |  | 	errTierReservedName = AdminError{ | 
					
						
							|  |  |  | 		Code:       "XMinioAdminTierReserved", | 
					
						
							|  |  |  | 		Message:    "Cannot use reserved tier name", | 
					
						
							|  |  |  | 		StatusCode: http.StatusBadRequest, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08: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 09:14:38 +08:00
										 |  |  | 	objAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SetTierAction) | 
					
						
							| 
									
										
										
										
											2023-05-16 14:56:52 +08:00
										 |  |  | 	if objAPI == nil { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		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-03 01:15:06 +08:00
										 |  |  | 	json := jsoniter.ConfigCompatibleWithStandardLibrary | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if err := json.Unmarshal(reqBytes, &cfg); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-28 01:26:26 +08:00
										 |  |  | 	var ignoreInUse bool | 
					
						
							|  |  |  | 	if forceStr := r.Form.Get("force"); forceStr != "" { | 
					
						
							|  |  |  | 		ignoreInUse, _ = strconv.ParseBool(forceStr) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-24 09:38:33 +08: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-09 03:18:07 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08: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-28 01:26:26 +08:00
										 |  |  | 	err = globalTierConfigMgr.Add(ctx, cfg, ignoreInUse) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08: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 09:14:38 +08:00
										 |  |  | 	objAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListTierAction) | 
					
						
							| 
									
										
										
										
											2023-05-16 14:56:52 +08:00
										 |  |  | 	if objAPI == nil { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		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 09:14:38 +08:00
										 |  |  | 	objAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SetTierAction) | 
					
						
							| 
									
										
										
										
											2023-05-16 14:56:52 +08:00
										 |  |  | 	if objAPI == nil { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		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-03 01:15:06 +08:00
										 |  |  | 	json := jsoniter.ConfigCompatibleWithStandardLibrary | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08: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-24 05:34:25 +08: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) | 
					
						
							| 
									
										
										
										
											2023-05-16 14:56:52 +08:00
										 |  |  | 	if objAPI == nil { | 
					
						
							| 
									
										
										
										
											2022-02-24 05:34:25 +08:00
										 |  |  | 		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) | 
					
						
							| 
									
										
										
										
											2023-05-16 14:56:52 +08:00
										 |  |  | 	if objAPI == nil { | 
					
						
							| 
									
										
										
										
											2022-02-24 05:34:25 +08:00
										 |  |  | 		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-20 01:30:42 +08:00
										 |  |  | 	writeSuccessNoContent(w) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-10-24 09:38:33 +08: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) | 
					
						
							| 
									
										
										
										
											2023-05-16 14:56:52 +08:00
										 |  |  | 	if objAPI == nil { | 
					
						
							| 
									
										
										
										
											2021-10-24 09:38:33 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dui, err := loadDataUsageFromBackend(ctx, objAPI) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 06:33:10 +08:00
										 |  |  | 	tierStats := dui.tierStats() | 
					
						
							|  |  |  | 	dailyStats := globalNotificationSys.GetLastDayTierStats(ctx) | 
					
						
							|  |  |  | 	tierStats = dailyStats.addToTierInfo(tierStats) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data, err := json.Marshal(tierStats) | 
					
						
							| 
									
										
										
										
											2021-10-24 09:38:33 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	writeSuccessResponseJSON(w, data) | 
					
						
							|  |  |  | } |