github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/tier-handlers.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "encoding/json" 22 "io" 23 "net/http" 24 "strconv" 25 26 jsoniter "github.com/json-iterator/go" 27 "github.com/minio/madmin-go/v3" 28 "github.com/minio/minio/internal/config/storageclass" 29 "github.com/minio/mux" 30 "github.com/minio/pkg/v2/policy" 31 ) 32 33 var ( 34 // error returned when remote tier already exists 35 errTierAlreadyExists = AdminError{ 36 Code: "XMinioAdminTierAlreadyExists", 37 Message: "Specified remote tier already exists", 38 StatusCode: http.StatusConflict, 39 } 40 // error returned when remote tier is not found 41 errTierNotFound = AdminError{ 42 Code: "XMinioAdminTierNotFound", 43 Message: "Specified remote tier was not found", 44 StatusCode: http.StatusNotFound, 45 } 46 // error returned when remote tier name is not in uppercase 47 errTierNameNotUppercase = AdminError{ 48 Code: "XMinioAdminTierNameNotUpperCase", 49 Message: "Tier name must be in uppercase", 50 StatusCode: http.StatusBadRequest, 51 } 52 // error returned when remote tier bucket is not found 53 errTierBucketNotFound = AdminError{ 54 Code: "XMinioAdminTierBucketNotFound", 55 Message: "Remote tier bucket not found", 56 StatusCode: http.StatusBadRequest, 57 } 58 // error returned when remote tier credentials are invalid. 59 errTierInvalidCredentials = AdminError{ 60 Code: "XMinioAdminTierInvalidCredentials", 61 Message: "Invalid remote tier credentials", 62 StatusCode: http.StatusBadRequest, 63 } 64 // error returned when reserved internal names are used. 65 errTierReservedName = AdminError{ 66 Code: "XMinioAdminTierReserved", 67 Message: "Cannot use reserved tier name", 68 StatusCode: http.StatusBadRequest, 69 } 70 ) 71 72 func (api adminAPIHandlers) AddTierHandler(w http.ResponseWriter, r *http.Request) { 73 ctx := r.Context() 74 75 objAPI, cred := validateAdminReq(ctx, w, r, policy.SetTierAction) 76 if objAPI == nil { 77 return 78 } 79 80 password := cred.SecretKey 81 reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength)) 82 if err != nil { 83 writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL) 84 return 85 } 86 87 var cfg madmin.TierConfig 88 json := jsoniter.ConfigCompatibleWithStandardLibrary 89 if err := json.Unmarshal(reqBytes, &cfg); err != nil { 90 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 91 return 92 } 93 94 var ignoreInUse bool 95 if forceStr := r.Form.Get("force"); forceStr != "" { 96 ignoreInUse, _ = strconv.ParseBool(forceStr) 97 } 98 99 // Disallow remote tiers with internal storage class names 100 switch cfg.Name { 101 case storageclass.STANDARD, storageclass.RRS: 102 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errTierReservedName), r.URL) 103 return 104 } 105 106 // Refresh from the disk in case we had missed notifications about edits from peers. 107 if err := globalTierConfigMgr.Reload(ctx, objAPI); err != nil { 108 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 109 return 110 } 111 112 err = globalTierConfigMgr.Add(ctx, cfg, ignoreInUse) 113 if err != nil { 114 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 115 return 116 } 117 118 err = globalTierConfigMgr.Save(ctx, objAPI) 119 if err != nil { 120 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 121 return 122 } 123 globalNotificationSys.LoadTransitionTierConfig(ctx) 124 125 writeSuccessNoContent(w) 126 } 127 128 func (api adminAPIHandlers) ListTierHandler(w http.ResponseWriter, r *http.Request) { 129 ctx := r.Context() 130 131 objAPI, _ := validateAdminReq(ctx, w, r, policy.ListTierAction) 132 if objAPI == nil { 133 return 134 } 135 136 tiers := globalTierConfigMgr.ListTiers() 137 data, err := json.Marshal(tiers) 138 if err != nil { 139 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 140 return 141 } 142 143 w.Header().Set(tierCfgRefreshAtHdr, globalTierConfigMgr.refreshedAt().String()) 144 writeSuccessResponseJSON(w, data) 145 } 146 147 func (api adminAPIHandlers) EditTierHandler(w http.ResponseWriter, r *http.Request) { 148 ctx := r.Context() 149 150 objAPI, cred := validateAdminReq(ctx, w, r, policy.SetTierAction) 151 if objAPI == nil { 152 return 153 } 154 vars := mux.Vars(r) 155 scName := vars["tier"] 156 157 password := cred.SecretKey 158 reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength)) 159 if err != nil { 160 writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL) 161 return 162 } 163 164 var creds madmin.TierCreds 165 json := jsoniter.ConfigCompatibleWithStandardLibrary 166 if err := json.Unmarshal(reqBytes, &creds); err != nil { 167 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 168 return 169 } 170 171 // Refresh from the disk in case we had missed notifications about edits from peers. 172 if err := globalTierConfigMgr.Reload(ctx, objAPI); err != nil { 173 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 174 return 175 } 176 177 if err := globalTierConfigMgr.Edit(ctx, scName, creds); err != nil { 178 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 179 return 180 } 181 182 if err := globalTierConfigMgr.Save(ctx, objAPI); err != nil { 183 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 184 return 185 } 186 globalNotificationSys.LoadTransitionTierConfig(ctx) 187 188 writeSuccessNoContent(w) 189 } 190 191 func (api adminAPIHandlers) RemoveTierHandler(w http.ResponseWriter, r *http.Request) { 192 ctx := r.Context() 193 194 objAPI, _ := validateAdminReq(ctx, w, r, policy.SetTierAction) 195 if objAPI == nil { 196 return 197 } 198 199 vars := mux.Vars(r) 200 tier := vars["tier"] 201 if err := globalTierConfigMgr.Reload(ctx, objAPI); err != nil { 202 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 203 return 204 } 205 206 if err := globalTierConfigMgr.Remove(ctx, tier); err != nil { 207 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 208 return 209 } 210 211 if err := globalTierConfigMgr.Save(ctx, objAPI); err != nil { 212 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 213 return 214 } 215 globalNotificationSys.LoadTransitionTierConfig(ctx) 216 217 writeSuccessNoContent(w) 218 } 219 220 func (api adminAPIHandlers) VerifyTierHandler(w http.ResponseWriter, r *http.Request) { 221 ctx := r.Context() 222 223 objAPI, _ := validateAdminReq(ctx, w, r, policy.ListTierAction) 224 if objAPI == nil { 225 return 226 } 227 228 vars := mux.Vars(r) 229 tier := vars["tier"] 230 if err := globalTierConfigMgr.Verify(ctx, tier); err != nil { 231 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 232 return 233 } 234 235 writeSuccessNoContent(w) 236 } 237 238 func (api adminAPIHandlers) TierStatsHandler(w http.ResponseWriter, r *http.Request) { 239 ctx := r.Context() 240 241 objAPI, _ := validateAdminReq(ctx, w, r, policy.ListTierAction) 242 if objAPI == nil { 243 return 244 } 245 246 dui, err := loadDataUsageFromBackend(ctx, objAPI) 247 if err != nil { 248 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 249 return 250 } 251 252 tierStats := dui.tierStats() 253 dailyStats := globalNotificationSys.GetLastDayTierStats(ctx) 254 tierStats = dailyStats.addToTierInfo(tierStats) 255 256 data, err := json.Marshal(tierStats) 257 if err != nil { 258 writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 259 return 260 } 261 writeSuccessResponseJSON(w, data) 262 }