| 
									
										
										
										
											2022-01-11 01:07:49 +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" | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2022-07-02 07:21:23 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2023-12-07 17:33:56 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2023-01-17 00:06:34 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/minio/minio/internal/logger" | 
					
						
							| 
									
										
										
										
											2023-01-23 19:12:47 +08:00
										 |  |  | 	"github.com/minio/mux" | 
					
						
							| 
									
										
										
										
											2023-09-15 05:50:16 +08:00
										 |  |  | 	"github.com/minio/pkg/v2/policy" | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | var ( | 
					
						
							| 
									
										
										
										
											2023-07-21 23:45:08 +08:00
										 |  |  | 	errRebalanceDecommissionAlreadyRunning = errors.New("Rebalance cannot be started, decommission is already in progress") | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 	errDecommissionRebalanceAlreadyRunning = errors.New("Decommission cannot be started, rebalance is already in progress") | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2023-07-14 05:52:21 +08:00
										 |  |  | 	ctx := r.Context() | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 05:50:16 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction) | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Legacy args style such as non-ellipses style is not supported with this API.
 | 
					
						
							|  |  |  | 	if globalEndpoints.Legacy() { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-17 00:06:34 +08:00
										 |  |  | 	z, ok := objectAPI.(*erasureServerPools) | 
					
						
							|  |  |  | 	if !ok || len(z.serverPools) == 1 { | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-17 00:06:34 +08:00
										 |  |  | 	if z.IsDecommissionRunning() { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errDecommissionAlreadyRunning), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if z.IsRebalanceStarted() { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceAlreadyStarted), r.URL) | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  | 	v := vars["pool"] | 
					
						
							| 
									
										
										
										
											2023-12-07 17:33:56 +08:00
										 |  |  | 	byID := vars["by-id"] == "true" | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-17 00:06:34 +08:00
										 |  |  | 	pools := strings.Split(v, ",") | 
					
						
							|  |  |  | 	poolIndices := make([]int, 0, len(pools)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, pool := range pools { | 
					
						
							| 
									
										
										
										
											2023-12-07 17:33:56 +08:00
										 |  |  | 		var idx int | 
					
						
							|  |  |  | 		if byID { | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			idx, err = strconv.Atoi(pool) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				// We didn't find any matching pools, invalid input
 | 
					
						
							|  |  |  | 				writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			idx = globalEndpoints.GetPoolIdx(pool) | 
					
						
							|  |  |  | 			if idx == -1 { | 
					
						
							|  |  |  | 				// We didn't find any matching pools, invalid input
 | 
					
						
							|  |  |  | 				writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-01-17 00:06:34 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		var pool *erasureSets | 
					
						
							|  |  |  | 		for pidx := range z.serverPools { | 
					
						
							|  |  |  | 			if pidx == idx { | 
					
						
							|  |  |  | 				pool = z.serverPools[idx] | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if pool == nil { | 
					
						
							|  |  |  | 			// We didn't find any matching pools, invalid input
 | 
					
						
							|  |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		poolIndices = append(poolIndices, idx) | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-16 05:54:29 +08:00
										 |  |  | 	if len(poolIndices) > 0 && !globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal { | 
					
						
							| 
									
										
										
										
											2023-01-17 00:06:34 +08:00
										 |  |  | 		ep := globalEndpoints[poolIndices[0]].Endpoints[0] | 
					
						
							| 
									
										
										
										
											2022-04-07 14:42:05 +08:00
										 |  |  | 		for nodeIdx, proxyEp := range globalProxyEndpoints { | 
					
						
							|  |  |  | 			if proxyEp.Endpoint.Host == ep.Host { | 
					
						
							|  |  |  | 				if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-17 00:06:34 +08:00
										 |  |  | 	if err := z.Decommission(r.Context(), poolIndices...); err != nil { | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2023-07-14 05:52:21 +08:00
										 |  |  | 	ctx := r.Context() | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 05:50:16 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction) | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Legacy args style such as non-ellipses style is not supported with this API.
 | 
					
						
							|  |  |  | 	if globalEndpoints.Legacy() { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pools, ok := objectAPI.(*erasureServerPools) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  | 	v := vars["pool"] | 
					
						
							| 
									
										
										
										
											2023-12-07 17:33:56 +08:00
										 |  |  | 	byID := vars["by-id"] == "true" | 
					
						
							|  |  |  | 	idx := -1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if byID { | 
					
						
							|  |  |  | 		if i, err := strconv.Atoi(v); err == nil && i >= 0 && i < len(globalEndpoints) { | 
					
						
							|  |  |  | 			idx = i | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		idx = globalEndpoints.GetPoolIdx(v) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if idx == -1 { | 
					
						
							|  |  |  | 		// We didn't find any matching pools, invalid input
 | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 14:42:05 +08:00
										 |  |  | 	if ep := globalEndpoints[idx].Endpoints[0]; !ep.IsLocal { | 
					
						
							|  |  |  | 		for nodeIdx, proxyEp := range globalProxyEndpoints { | 
					
						
							|  |  |  | 			if proxyEp.Endpoint.Host == ep.Host { | 
					
						
							|  |  |  | 				if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 	if err := pools.DecommissionCancel(ctx, idx); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2023-07-14 05:52:21 +08:00
										 |  |  | 	ctx := r.Context() | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 05:50:16 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction) | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Legacy args style such as non-ellipses style is not supported with this API.
 | 
					
						
							|  |  |  | 	if globalEndpoints.Legacy() { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pools, ok := objectAPI.(*erasureServerPools) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  | 	v := vars["pool"] | 
					
						
							| 
									
										
										
										
											2023-12-07 17:33:56 +08:00
										 |  |  | 	byID := vars["by-id"] == "true" | 
					
						
							|  |  |  | 	idx := -1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if byID { | 
					
						
							|  |  |  | 		if i, err := strconv.Atoi(v); err == nil && i >= 0 && i < len(globalEndpoints) { | 
					
						
							|  |  |  | 			idx = i | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		idx = globalEndpoints.GetPoolIdx(v) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if idx == -1 { | 
					
						
							| 
									
										
										
										
											2022-07-02 07:21:23 +08:00
										 |  |  | 		apiErr := toAdminAPIErr(ctx, errInvalidArgument) | 
					
						
							|  |  |  | 		apiErr.Description = fmt.Sprintf("specified pool '%s' not found, please specify a valid pool", v) | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 		// We didn't find any matching pools, invalid input
 | 
					
						
							| 
									
										
										
										
											2022-07-02 07:21:23 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, apiErr, r.URL) | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	status, err := pools.Status(r.Context(), idx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	logger.LogIf(r.Context(), json.NewEncoder(w).Encode(&status)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2023-07-14 05:52:21 +08:00
										 |  |  | 	ctx := r.Context() | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 05:50:16 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction) | 
					
						
							| 
									
										
										
										
											2022-01-11 01:07:49 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Legacy args style such as non-ellipses style is not supported with this API.
 | 
					
						
							|  |  |  | 	if globalEndpoints.Legacy() { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pools, ok := objectAPI.(*erasureServerPools) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	poolsStatus := make([]PoolStatus, len(globalEndpoints)) | 
					
						
							|  |  |  | 	for idx := range globalEndpoints { | 
					
						
							|  |  |  | 		status, err := pools.Status(r.Context(), idx) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		poolsStatus[idx] = status | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	logger.LogIf(r.Context(), json.NewEncoder(w).Encode(poolsStatus)) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2023-07-14 05:52:21 +08:00
										 |  |  | 	ctx := r.Context() | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 05:50:16 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction) | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NB rebalance-start admin API is always coordinated from first pool's
 | 
					
						
							|  |  |  | 	// first node. The following is required to serialize (the effects of)
 | 
					
						
							|  |  |  | 	// concurrent rebalance-start commands.
 | 
					
						
							|  |  |  | 	if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal { | 
					
						
							|  |  |  | 		for nodeIdx, proxyEp := range globalProxyEndpoints { | 
					
						
							|  |  |  | 			if proxyEp.Endpoint.Host == ep.Host { | 
					
						
							|  |  |  | 				if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pools, ok := objectAPI.(*erasureServerPools) | 
					
						
							|  |  |  | 	if !ok || len(pools.serverPools) == 1 { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if pools.IsDecommissionRunning() { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errRebalanceDecommissionAlreadyRunning), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if pools.IsRebalanceStarted() { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceAlreadyStarted), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucketInfos, err := objectAPI.ListBuckets(ctx, BucketOptions{}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	buckets := make([]string, 0, len(bucketInfos)) | 
					
						
							|  |  |  | 	for _, bInfo := range bucketInfos { | 
					
						
							|  |  |  | 		buckets = append(buckets, bInfo.Name) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var id string | 
					
						
							|  |  |  | 	if id, err = pools.initRebalanceMeta(ctx, buckets); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Rebalance routine is run on the first node of any pool participating in rebalance.
 | 
					
						
							|  |  |  | 	pools.StartRebalance() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	b, err := json.Marshal(struct { | 
					
						
							|  |  |  | 		ID string `json:"id"` | 
					
						
							|  |  |  | 	}{ID: id}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	writeSuccessResponseJSON(w, b) | 
					
						
							|  |  |  | 	// Notify peers to load rebalance.bin and start rebalance routine if they happen to be
 | 
					
						
							|  |  |  | 	// participating pool's leader node
 | 
					
						
							|  |  |  | 	globalNotificationSys.LoadRebalanceMeta(ctx, true) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2023-07-14 05:52:21 +08:00
										 |  |  | 	ctx := r.Context() | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 05:50:16 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction) | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Proxy rebalance-status to first pool first node, so that users see a
 | 
					
						
							|  |  |  | 	// consistent view of rebalance progress even though different rebalancing
 | 
					
						
							|  |  |  | 	// pools may temporarily have out of date info on the others.
 | 
					
						
							|  |  |  | 	if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal { | 
					
						
							|  |  |  | 		for nodeIdx, proxyEp := range globalProxyEndpoints { | 
					
						
							|  |  |  | 			if proxyEp.Endpoint.Host == ep.Host { | 
					
						
							|  |  |  | 				if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pools, ok := objectAPI.(*erasureServerPools) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rs, err := rebalanceStatus(ctx, pools) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-11-08 23:56:45 +08:00
										 |  |  | 		if errors.Is(err, errRebalanceNotStarted) || errors.Is(err, errConfigNotFound) { | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 			writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceNotStarted), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		logger.LogIf(ctx, fmt.Errorf("failed to fetch rebalance status: %w", err)) | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	logger.LogIf(r.Context(), json.NewEncoder(w).Encode(rs)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2023-07-14 05:52:21 +08:00
										 |  |  | 	ctx := r.Context() | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 05:50:16 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction) | 
					
						
							| 
									
										
										
										
											2022-10-26 03:36:57 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pools, ok := objectAPI.(*erasureServerPools) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Cancel any ongoing rebalance operation
 | 
					
						
							|  |  |  | 	globalNotificationSys.StopRebalance(r.Context()) | 
					
						
							|  |  |  | 	writeSuccessResponseHeadersOnly(w) | 
					
						
							|  |  |  | 	logger.LogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt)) | 
					
						
							|  |  |  | } |