| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  |  * MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc. | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2019-09-05 04:19:44 +08:00
										 |  |  | 	"crypto/subtle" | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2018-01-11 15:06:36 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	"path" | 
					
						
							|  |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2019-01-24 23:22:14 +08:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2018-09-06 23:03:18 +08:00
										 |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2017-11-01 02:54:32 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 10:56:32 +08:00
										 |  |  | 	humanize "github.com/dustin/go-humanize" | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	"github.com/gorilla/mux" | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-23 13:59:13 +08:00
										 |  |  | 	"github.com/minio/minio/cmd/config" | 
					
						
							| 
									
										
										
										
											2019-09-05 04:19:44 +08:00
										 |  |  | 	"github.com/minio/minio/cmd/crypto" | 
					
						
							| 
									
										
										
										
											2019-07-03 13:34:32 +08:00
										 |  |  | 	xhttp "github.com/minio/minio/cmd/http" | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 	"github.com/minio/minio/cmd/logger" | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/auth" | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/cpu" | 
					
						
							| 
									
										
										
										
											2018-03-03 07:23:04 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/handlers" | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	iampolicy "github.com/minio/minio/pkg/iam/policy" | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/madmin" | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/mem" | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 	xnet "github.com/minio/minio/pkg/net" | 
					
						
							| 
									
										
										
										
											2019-06-27 13:41:12 +08:00
										 |  |  | 	trace "github.com/minio/minio/pkg/trace" | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							| 
									
										
										
										
											2018-10-10 05:00:01 +08:00
										 |  |  | 	maxEConfigJSONSize = 262272 | 
					
						
							| 
									
										
										
										
											2019-08-19 10:56:32 +08:00
										 |  |  | 	defaultNetPerfSize = 100 * humanize.MiByte | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | // Type-safe query params.
 | 
					
						
							|  |  |  | type mgmtQueryKey string | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-16 15:15:06 +08:00
										 |  |  | // Only valid query params for mgmt admin APIs.
 | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | const ( | 
					
						
							| 
									
										
										
										
											2018-08-18 09:51:34 +08:00
										 |  |  | 	mgmtBucket      mgmtQueryKey = "bucket" | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 	mgmtPrefix                   = "prefix" | 
					
						
							|  |  |  | 	mgmtClientToken              = "clientToken" | 
					
						
							|  |  |  | 	mgmtForceStart               = "forceStart" | 
					
						
							|  |  |  | 	mgmtForceStop                = "forceStop" | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | func updateServer(updateURL, sha256Hex string, latestReleaseTime time.Time) (us madmin.ServerUpdateStatus, err error) { | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 	minioMode := getMinioMode() | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	// No inputs provided we should try to update using the default URL.
 | 
					
						
							|  |  |  | 	if updateURL == "" && sha256Hex == "" && latestReleaseTime.IsZero() { | 
					
						
							|  |  |  | 		var updateMsg string | 
					
						
							|  |  |  | 		updateMsg, sha256Hex, _, latestReleaseTime, err = getUpdateInfo(updateTimeout, minioMode) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return us, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if updateMsg == "" { | 
					
						
							|  |  |  | 			us.CurrentVersion = Version | 
					
						
							|  |  |  | 			us.UpdatedVersion = Version | 
					
						
							|  |  |  | 			return us, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if runtime.GOOS == "windows" { | 
					
						
							|  |  |  | 			updateURL = minioReleaseURL + "minio.exe" | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			updateURL = minioReleaseURL + "minio" | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	if err = doUpdate(updateURL, sha256Hex, minioMode); err != nil { | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 		return us, err | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 	us.CurrentVersion = Version | 
					
						
							|  |  |  | 	us.UpdatedVersion = latestReleaseTime.Format(minioReleaseTagTimeLayout) | 
					
						
							|  |  |  | 	return us, nil | 
					
						
							| 
									
										
										
										
											2017-01-24 00:56:06 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // ServerUpdateHandler - POST /minio/admin/v2/update?updateURL={updateURL}
 | 
					
						
							| 
									
										
										
										
											2016-12-21 10:49:48 +08:00
										 |  |  | // ----------
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | // updates all minio servers and restarts them gracefully.
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "ServerUpdate") | 
					
						
							| 
									
										
										
										
											2018-11-13 03:07:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerUpdateAdminAction) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-24 00:56:06 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	if globalInplaceUpdateDisabled { | 
					
						
							|  |  |  | 		// if MINIO_UPDATE=off - inplace update is disabled, mostly
 | 
					
						
							|  |  |  | 		// in containers.
 | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL) | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 		return | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-24 00:56:06 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  | 	updateURL := vars[peerRESTUpdateURL] | 
					
						
							|  |  |  | 	mode := getMinioMode() | 
					
						
							|  |  |  | 	var sha256Hex string | 
					
						
							|  |  |  | 	var latestReleaseTime time.Time | 
					
						
							|  |  |  | 	if updateURL != "" { | 
					
						
							|  |  |  | 		u, err := url.Parse(updateURL) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-02-08 16:13:02 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 		content, err := downloadReleaseURL(updateURL, updateTimeout, mode) | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-01-24 00:56:06 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 		sha256Hex, latestReleaseTime, err = parseReleaseData(content) | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 		if runtime.GOOS == "windows" { | 
					
						
							|  |  |  | 			u.Path = path.Dir(u.Path) + "minio.exe" | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			u.Path = path.Dir(u.Path) + "minio" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		updateURL = u.String() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	for _, nerr := range globalNotificationSys.ServerUpdate(updateURL, sha256Hex, latestReleaseTime) { | 
					
						
							|  |  |  | 		if nerr.Err != nil { | 
					
						
							|  |  |  | 			logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) | 
					
						
							|  |  |  | 			logger.LogIf(ctx, nerr.Err) | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	updateStatus, err := updateServer(updateURL, sha256Hex, latestReleaseTime) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							| 
									
										
										
										
											2016-12-21 10:05:25 +08:00
										 |  |  | 		return | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	// Marshal API response
 | 
					
						
							|  |  |  | 	jsonBytes, err := json.Marshal(updateStatus) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							| 
									
										
										
										
											2018-11-13 03:07:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	if updateStatus.CurrentVersion != updateStatus.UpdatedVersion { | 
					
						
							|  |  |  | 		// We did upgrade - restart all services.
 | 
					
						
							|  |  |  | 		globalServiceSignalCh <- serviceRestart | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // ServiceActionHandler - POST /minio/admin/v2/service?action={action}
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | // ----------
 | 
					
						
							|  |  |  | // restarts/stops minio server gracefully. In a distributed setup,
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "ServiceAction") | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  | 	action := vars["action"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, "") | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var serviceSig serviceSignal | 
					
						
							|  |  |  | 	switch madmin.ServiceAction(action) { | 
					
						
							|  |  |  | 	case madmin.ServiceActionRestart: | 
					
						
							|  |  |  | 		serviceSig = serviceRestart | 
					
						
							|  |  |  | 	case madmin.ServiceActionStop: | 
					
						
							|  |  |  | 		serviceSig = serviceStop | 
					
						
							|  |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2019-10-12 09:50:54 +08:00
										 |  |  | 		logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.Application) | 
					
						
							| 
									
										
										
										
											2019-08-29 06:04:43 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-10 02:39:42 +08:00
										 |  |  | 	// Notify all other MinIO peers signal service.
 | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 	for _, nerr := range globalNotificationSys.SignalService(serviceSig) { | 
					
						
							|  |  |  | 		if nerr.Err != nil { | 
					
						
							|  |  |  | 			logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) | 
					
						
							|  |  |  | 			logger.LogIf(ctx, nerr.Err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 	// Reply to the client before restarting, stopping MinIO server.
 | 
					
						
							|  |  |  | 	writeSuccessResponseHeadersOnly(w) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 	globalServiceSignalCh <- serviceSig | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | // ServerProperties holds some server information such as, version, region
 | 
					
						
							|  |  |  | // uptime, etc..
 | 
					
						
							|  |  |  | type ServerProperties struct { | 
					
						
							| 
									
										
										
										
											2019-03-19 18:42:24 +08:00
										 |  |  | 	Uptime       time.Duration `json:"uptime"` | 
					
						
							|  |  |  | 	Version      string        `json:"version"` | 
					
						
							|  |  |  | 	CommitID     string        `json:"commitID"` | 
					
						
							|  |  |  | 	DeploymentID string        `json:"deploymentID"` | 
					
						
							|  |  |  | 	Region       string        `json:"region"` | 
					
						
							|  |  |  | 	SQSARN       []string      `json:"sqsARN"` | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServerConnStats holds transferred bytes from/to the server
 | 
					
						
							|  |  |  | type ServerConnStats struct { | 
					
						
							|  |  |  | 	TotalInputBytes  uint64 `json:"transferred"` | 
					
						
							|  |  |  | 	TotalOutputBytes uint64 `json:"received"` | 
					
						
							|  |  |  | 	Throughput       uint64 `json:"throughput,omitempty"` | 
					
						
							| 
									
										
										
										
											2019-10-23 12:01:14 +08:00
										 |  |  | 	S3InputBytes     uint64 `json:"transferredS3"` | 
					
						
							|  |  |  | 	S3OutputBytes    uint64 `json:"receivedS3"` | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-23 12:01:14 +08:00
										 |  |  | // ServerHTTPAPIStats holds total number of HTTP operations from/to the server,
 | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | // including the average duration the call was spent.
 | 
					
						
							| 
									
										
										
										
											2019-10-23 12:01:14 +08:00
										 |  |  | type ServerHTTPAPIStats struct { | 
					
						
							|  |  |  | 	APIStats map[string]int `json:"apiStats"` | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServerHTTPStats holds all type of http operations performed to/from the server
 | 
					
						
							|  |  |  | // including their average execution time.
 | 
					
						
							|  |  |  | type ServerHTTPStats struct { | 
					
						
							| 
									
										
										
										
											2019-10-23 12:01:14 +08:00
										 |  |  | 	CurrentS3Requests ServerHTTPAPIStats `json:"currentS3Requests"` | 
					
						
							|  |  |  | 	TotalS3Requests   ServerHTTPAPIStats `json:"totalS3Requests"` | 
					
						
							|  |  |  | 	TotalS3Errors     ServerHTTPAPIStats `json:"totalS3Errors"` | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | // ServerInfoData holds storage, connections and other
 | 
					
						
							|  |  |  | // information of a given server.
 | 
					
						
							|  |  |  | type ServerInfoData struct { | 
					
						
							| 
									
										
										
										
											2019-10-23 12:01:14 +08:00
										 |  |  | 	ConnStats  ServerConnStats  `json:"network"` | 
					
						
							|  |  |  | 	HTTPStats  ServerHTTPStats  `json:"http"` | 
					
						
							|  |  |  | 	Properties ServerProperties `json:"server"` | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | // ServerInfo holds server information result of one node
 | 
					
						
							|  |  |  | type ServerInfo struct { | 
					
						
							| 
									
										
										
										
											2017-05-15 22:28:47 +08:00
										 |  |  | 	Error string          `json:"error"` | 
					
						
							|  |  |  | 	Addr  string          `json:"addr"` | 
					
						
							|  |  |  | 	Data  *ServerInfoData `json:"data"` | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // ServerInfoHandler - GET /minio/admin/v2/info
 | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | // ----------
 | 
					
						
							|  |  |  | // Get server information
 | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-11-13 03:07:43 +08:00
										 |  |  | 	ctx := newContext(r, w, "ServerInfo") | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 	serverInfo := globalNotificationSys.ServerInfo(ctx) | 
					
						
							| 
									
										
										
										
											2019-01-17 04:49:20 +08:00
										 |  |  | 	// Once we have received all the ServerInfo from peers
 | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 	// add the local peer server info as well.
 | 
					
						
							|  |  |  | 	serverInfo = append(serverInfo, ServerInfo{ | 
					
						
							| 
									
										
										
										
											2019-07-19 00:58:37 +08:00
										 |  |  | 		Addr: getHostName(r), | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 		Data: &ServerInfoData{ | 
					
						
							| 
									
										
										
										
											2019-10-23 12:01:14 +08:00
										 |  |  | 			ConnStats: globalConnStats.toServerConnStats(), | 
					
						
							|  |  |  | 			HTTPStats: globalHTTPStats.toServerHTTPStats(), | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 			Properties: ServerProperties{ | 
					
						
							| 
									
										
										
										
											2019-03-19 18:42:24 +08:00
										 |  |  | 				Uptime:       UTCNow().Sub(globalBootTime), | 
					
						
							|  |  |  | 				Version:      Version, | 
					
						
							|  |  |  | 				CommitID:     CommitID, | 
					
						
							|  |  |  | 				DeploymentID: globalDeploymentID, | 
					
						
							|  |  |  | 				SQSARN:       globalNotificationSys.GetARNList(), | 
					
						
							| 
									
										
										
										
											2019-10-23 13:59:13 +08:00
										 |  |  | 				Region:       globalServerRegion, | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 	// Marshal API response
 | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 	jsonBytes, err := json.Marshal(serverInfo) | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 	// Reply with storage information (across nodes in a
 | 
					
						
							|  |  |  | 	// distributed setup) as json.
 | 
					
						
							|  |  |  | 	writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // ServerInfoHandler - GET /minio/admin/v2/storageinfo
 | 
					
						
							| 
									
										
										
										
											2019-10-23 12:01:14 +08:00
										 |  |  | // ----------
 | 
					
						
							|  |  |  | // Get server information
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "StorageInfo") | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) | 
					
						
							| 
									
										
										
										
											2019-10-23 12:01:14 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	storageInfo := objectAPI.StorageInfo(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Marshal API response
 | 
					
						
							|  |  |  | 	jsonBytes, err := json.Marshal(storageInfo) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reply with storage information (across nodes in a
 | 
					
						
							|  |  |  | 	// distributed setup) as json.
 | 
					
						
							|  |  |  | 	writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | // ServerCPULoadInfo holds informantion about cpu utilization
 | 
					
						
							|  |  |  | // of one minio node. It also reports any errors if encountered
 | 
					
						
							|  |  |  | // while trying to reach this server.
 | 
					
						
							|  |  |  | type ServerCPULoadInfo struct { | 
					
						
							| 
									
										
										
										
											2019-01-30 15:17:32 +08:00
										 |  |  | 	Addr         string     `json:"addr"` | 
					
						
							|  |  |  | 	Error        string     `json:"error,omitempty"` | 
					
						
							|  |  |  | 	Load         []cpu.Load `json:"load"` | 
					
						
							|  |  |  | 	HistoricLoad []cpu.Load `json:"historicLoad"` | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServerMemUsageInfo holds informantion about memory utilization
 | 
					
						
							|  |  |  | // of one minio node. It also reports any errors if encountered
 | 
					
						
							|  |  |  | // while trying to reach this server.
 | 
					
						
							|  |  |  | type ServerMemUsageInfo struct { | 
					
						
							| 
									
										
										
										
											2019-01-30 15:17:32 +08:00
										 |  |  | 	Addr          string      `json:"addr"` | 
					
						
							|  |  |  | 	Error         string      `json:"error,omitempty"` | 
					
						
							|  |  |  | 	Usage         []mem.Usage `json:"usage"` | 
					
						
							|  |  |  | 	HistoricUsage []mem.Usage `json:"historicUsage"` | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 10:56:32 +08:00
										 |  |  | // ServerNetReadPerfInfo network read performance information.
 | 
					
						
							|  |  |  | type ServerNetReadPerfInfo struct { | 
					
						
							| 
									
										
										
										
											2019-09-27 02:31:18 +08:00
										 |  |  | 	Addr           string `json:"addr"` | 
					
						
							|  |  |  | 	ReadThroughput uint64 `json:"readThroughput"` | 
					
						
							|  |  |  | 	Error          string `json:"error,omitempty"` | 
					
						
							| 
									
										
										
										
											2019-08-19 10:56:32 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // PerfInfoHandler - GET /minio/admin/v2/performance?perfType={perfType}
 | 
					
						
							| 
									
										
										
										
											2019-01-01 01:46:44 +08:00
										 |  |  | // ----------
 | 
					
						
							|  |  |  | // Get all performance information based on input type
 | 
					
						
							|  |  |  | // Supported types = drive
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "PerfInfo") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-01-01 01:46:44 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vars := mux.Vars(r) | 
					
						
							| 
									
										
										
										
											2019-03-18 22:46:20 +08:00
										 |  |  | 	switch perfType := vars["perfType"]; perfType { | 
					
						
							| 
									
										
										
										
											2019-08-19 10:56:32 +08:00
										 |  |  | 	case "net": | 
					
						
							|  |  |  | 		var size int64 = defaultNetPerfSize | 
					
						
							|  |  |  | 		if sizeStr, found := vars["size"]; found { | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			if size, err = strconv.ParseInt(sizeStr, 10, 64); err != nil || size < 0 { | 
					
						
							|  |  |  | 				writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-31 10:57:53 +08:00
										 |  |  | 		if !globalIsDistXL { | 
					
						
							| 
									
										
										
										
											2019-08-19 10:56:32 +08:00
										 |  |  | 			writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		addr := r.Host | 
					
						
							|  |  |  | 		if globalIsDistXL { | 
					
						
							|  |  |  | 			addr = GetLocalPeer(globalEndpoints) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		infos := map[string][]ServerNetReadPerfInfo{} | 
					
						
							|  |  |  | 		infos[addr] = globalNotificationSys.NetReadPerfInfo(size) | 
					
						
							|  |  |  | 		for peer, info := range globalNotificationSys.CollectNetPerfInfo(size) { | 
					
						
							|  |  |  | 			infos[peer] = info | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Marshal API response
 | 
					
						
							|  |  |  | 		jsonBytes, err := json.Marshal(infos) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Reply with performance information (across nodes in a
 | 
					
						
							|  |  |  | 		// distributed setup) as json.
 | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-18 22:46:20 +08:00
										 |  |  | 	case "drive": | 
					
						
							| 
									
										
										
										
											2019-09-13 05:52:30 +08:00
										 |  |  | 		// Drive Perf is only implemented for Erasure coded backends
 | 
					
						
							|  |  |  | 		if !globalIsXL { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 			writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL) | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-09-13 05:52:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		var size int64 = madmin.DefaultDrivePerfSize | 
					
						
							|  |  |  | 		if sizeStr, found := vars["size"]; found { | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			if size, err = strconv.ParseInt(sizeStr, 10, 64); err != nil || size <= 0 { | 
					
						
							|  |  |  | 				writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-01 01:46:44 +08:00
										 |  |  | 		// Get drive performance details from local server's drive(s)
 | 
					
						
							| 
									
										
										
										
											2019-09-13 05:52:30 +08:00
										 |  |  | 		dp := getLocalDrivesPerf(globalEndpoints, size, r) | 
					
						
							| 
									
										
										
										
											2019-01-01 01:46:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-10 02:39:42 +08:00
										 |  |  | 		// Notify all other MinIO peers to report drive performance numbers
 | 
					
						
							| 
									
										
										
										
											2019-09-13 05:52:30 +08:00
										 |  |  | 		dps := globalNotificationSys.DrivePerfInfo(size) | 
					
						
							| 
									
										
										
										
											2019-01-01 01:46:44 +08:00
										 |  |  | 		dps = append(dps, dp) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Marshal API response
 | 
					
						
							|  |  |  | 		jsonBytes, err := json.Marshal(dps) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							| 
									
										
										
										
											2019-01-01 01:46:44 +08:00
										 |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Reply with performance information (across nodes in a
 | 
					
						
							|  |  |  | 		// distributed setup) as json.
 | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							| 
									
										
										
										
											2019-03-18 22:46:20 +08:00
										 |  |  | 	case "cpu": | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | 		// Get CPU load details from local server's cpu(s)
 | 
					
						
							| 
									
										
										
										
											2019-09-13 02:06:12 +08:00
										 |  |  | 		cpu := getLocalCPULoad(globalEndpoints, r) | 
					
						
							| 
									
										
										
										
											2019-04-10 02:39:42 +08:00
										 |  |  | 		// Notify all other MinIO peers to report cpu load numbers
 | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | 		cpus := globalNotificationSys.CPULoadInfo() | 
					
						
							|  |  |  | 		cpus = append(cpus, cpu) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Marshal API response
 | 
					
						
							|  |  |  | 		jsonBytes, err := json.Marshal(cpus) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Reply with cpu load information (across nodes in a
 | 
					
						
							|  |  |  | 		// distributed setup) as json.
 | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							| 
									
										
										
										
											2019-03-18 22:46:20 +08:00
										 |  |  | 	case "mem": | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | 		// Get mem usage details from local server(s)
 | 
					
						
							| 
									
										
										
										
											2019-09-13 02:06:12 +08:00
										 |  |  | 		m := getLocalMemUsage(globalEndpoints, r) | 
					
						
							| 
									
										
										
										
											2019-04-10 02:39:42 +08:00
										 |  |  | 		// Notify all other MinIO peers to report mem usage numbers
 | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | 		mems := globalNotificationSys.MemUsageInfo() | 
					
						
							|  |  |  | 		mems = append(mems, m) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Marshal API response
 | 
					
						
							|  |  |  | 		jsonBytes, err := json.Marshal(mems) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							| 
									
										
										
										
											2019-01-10 11:04:19 +08:00
										 |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Reply with mem usage information (across nodes in a
 | 
					
						
							|  |  |  | 		// distributed setup) as json.
 | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							| 
									
										
										
										
											2019-03-18 22:46:20 +08:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL) | 
					
						
							| 
									
										
										
										
											2019-01-01 01:46:44 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-24 23:22:14 +08:00
										 |  |  | func newLockEntry(l lockRequesterInfo, resource, server string) *madmin.LockEntry { | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 	entry := &madmin.LockEntry{ | 
					
						
							|  |  |  | 		Timestamp:  l.Timestamp, | 
					
						
							|  |  |  | 		Resource:   resource, | 
					
						
							|  |  |  | 		ServerList: []string{server}, | 
					
						
							|  |  |  | 		Source:     l.Source, | 
					
						
							|  |  |  | 		ID:         l.UID, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-24 23:22:14 +08:00
										 |  |  | 	if l.Writer { | 
					
						
							|  |  |  | 		entry.Type = "Write" | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		entry.Type = "Read" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return entry | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func topLockEntries(peerLocks []*PeerLocks) madmin.LockEntries { | 
					
						
							|  |  |  | 	entryMap := make(map[string]*madmin.LockEntry) | 
					
						
							|  |  |  | 	for _, peerLock := range peerLocks { | 
					
						
							|  |  |  | 		if peerLock == nil { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 		for _, locks := range peerLock.Locks { | 
					
						
							|  |  |  | 			for k, v := range locks { | 
					
						
							|  |  |  | 				for _, lockReqInfo := range v { | 
					
						
							|  |  |  | 					if val, ok := entryMap[lockReqInfo.UID]; ok { | 
					
						
							|  |  |  | 						val.ServerList = append(val.ServerList, peerLock.Addr) | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						entryMap[lockReqInfo.UID] = newLockEntry(lockReqInfo, k, peerLock.Addr) | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2019-01-24 23:22:14 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var lockEntries = make(madmin.LockEntries, 0) | 
					
						
							|  |  |  | 	for _, v := range entryMap { | 
					
						
							|  |  |  | 		lockEntries = append(lockEntries, *v) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	sort.Sort(lockEntries) | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	const listCount int = 10 | 
					
						
							| 
									
										
										
										
											2019-01-24 23:22:14 +08:00
										 |  |  | 	if len(lockEntries) > listCount { | 
					
						
							|  |  |  | 		lockEntries = lockEntries[:listCount] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return lockEntries | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // PeerLocks holds server information result of one node
 | 
					
						
							|  |  |  | type PeerLocks struct { | 
					
						
							|  |  |  | 	Addr  string | 
					
						
							|  |  |  | 	Locks GetLocksResp | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TopLocksHandler Get list of locks in use
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "TopLocks") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-01-24 23:22:14 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	peerLocks := globalNotificationSys.GetLocks(ctx) | 
					
						
							|  |  |  | 	// Once we have received all the locks currently used from peers
 | 
					
						
							|  |  |  | 	// add the local peer locks list as well.
 | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 	var getRespLocks GetLocksResp | 
					
						
							|  |  |  | 	for _, llocker := range globalLockServers { | 
					
						
							|  |  |  | 		getRespLocks = append(getRespLocks, llocker.DupLockMap()) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-24 23:22:14 +08:00
										 |  |  | 	peerLocks = append(peerLocks, &PeerLocks{ | 
					
						
							| 
									
										
										
										
											2019-07-19 00:58:37 +08:00
										 |  |  | 		Addr:  getHostName(r), | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 		Locks: getRespLocks, | 
					
						
							| 
									
										
										
										
											2019-01-24 23:22:14 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	topLocks := topLockEntries(peerLocks) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Marshal API response
 | 
					
						
							|  |  |  | 	jsonBytes, err := json.Marshal(topLocks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							| 
									
										
										
										
											2019-01-24 23:22:14 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reply with storage information (across nodes in a
 | 
					
						
							|  |  |  | 	// distributed setup) as json.
 | 
					
						
							|  |  |  | 	writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | // StartProfilingResult contains the status of the starting
 | 
					
						
							|  |  |  | // profiling action in a given server
 | 
					
						
							|  |  |  | type StartProfilingResult struct { | 
					
						
							|  |  |  | 	NodeName string `json:"nodeName"` | 
					
						
							|  |  |  | 	Success  bool   `json:"success"` | 
					
						
							|  |  |  | 	Error    string `json:"error"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // StartProfilingHandler - POST /minio/admin/v2/profiling/start?profilerType={profilerType}
 | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | // ----------
 | 
					
						
							| 
									
										
										
										
											2018-09-27 12:02:05 +08:00
										 |  |  | // Enable server profiling
 | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-11-13 03:07:43 +08:00
										 |  |  | 	ctx := newContext(r, w, "StartProfiling") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vars := mux.Vars(r) | 
					
						
							| 
									
										
										
										
											2018-09-27 12:02:05 +08:00
										 |  |  | 	profiler := vars["profilerType"] | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 	thisAddr, err := xnet.ParseHost(GetLocalPeer(globalEndpoints)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start profiling on remote servers.
 | 
					
						
							|  |  |  | 	hostErrs := globalNotificationSys.StartProfiling(profiler) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start profiling locally as well.
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		if globalProfiler != nil { | 
					
						
							|  |  |  | 			globalProfiler.Stop() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		prof, err := startProfiler(profiler, "") | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			hostErrs = append(hostErrs, NotificationPeerErr{ | 
					
						
							|  |  |  | 				Host: *thisAddr, | 
					
						
							|  |  |  | 				Err:  err, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			globalProfiler = prof | 
					
						
							|  |  |  | 			hostErrs = append(hostErrs, NotificationPeerErr{ | 
					
						
							|  |  |  | 				Host: *thisAddr, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var startProfilingResult []StartProfilingResult | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, nerr := range hostErrs { | 
					
						
							|  |  |  | 		result := StartProfilingResult{NodeName: nerr.Host.String()} | 
					
						
							|  |  |  | 		if nerr.Err != nil { | 
					
						
							|  |  |  | 			result.Error = nerr.Err.Error() | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | 			result.Success = true | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		startProfilingResult = append(startProfilingResult, result) | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create JSON result and send it to the client
 | 
					
						
							|  |  |  | 	startProfilingResultInBytes, err := json.Marshal(startProfilingResult) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | 	writeSuccessResponseJSON(w, []byte(startProfilingResultInBytes)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // dummyFileInfo represents a dummy representation of a profile data file
 | 
					
						
							|  |  |  | // present only in memory, it helps to generate the zip stream.
 | 
					
						
							|  |  |  | type dummyFileInfo struct { | 
					
						
							|  |  |  | 	name    string | 
					
						
							|  |  |  | 	size    int64 | 
					
						
							|  |  |  | 	mode    os.FileMode | 
					
						
							|  |  |  | 	modTime time.Time | 
					
						
							|  |  |  | 	isDir   bool | 
					
						
							|  |  |  | 	sys     interface{} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (f dummyFileInfo) Name() string       { return f.name } | 
					
						
							|  |  |  | func (f dummyFileInfo) Size() int64        { return f.size } | 
					
						
							|  |  |  | func (f dummyFileInfo) Mode() os.FileMode  { return f.mode } | 
					
						
							|  |  |  | func (f dummyFileInfo) ModTime() time.Time { return f.modTime } | 
					
						
							|  |  |  | func (f dummyFileInfo) IsDir() bool        { return f.isDir } | 
					
						
							|  |  |  | func (f dummyFileInfo) Sys() interface{}   { return f.sys } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // DownloadProfilingHandler - POST /minio/admin/v2/profiling/download
 | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | // ----------
 | 
					
						
							|  |  |  | // Download profiling information of all nodes in a zip format
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-11-13 03:07:43 +08:00
										 |  |  | 	ctx := newContext(r, w, "DownloadProfiling") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-14 14:44:20 +08:00
										 |  |  | 	if !globalNotificationSys.DownloadProfilingData(ctx, w) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminProfilerNotEnabled), r.URL) | 
					
						
							| 
									
										
										
										
											2018-09-28 01:34:37 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-19 07:46:35 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | type healInitParams struct { | 
					
						
							|  |  |  | 	bucket, objPrefix     string | 
					
						
							|  |  |  | 	hs                    madmin.HealOpts | 
					
						
							|  |  |  | 	clientToken           string | 
					
						
							|  |  |  | 	forceStart, forceStop bool | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | // extractHealInitParams - Validates params for heal init API.
 | 
					
						
							|  |  |  | func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reader) (hip healInitParams, err APIErrorCode) { | 
					
						
							|  |  |  | 	hip.bucket = vars[string(mgmtBucket)] | 
					
						
							|  |  |  | 	hip.objPrefix = vars[string(mgmtPrefix)] | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	if hip.bucket == "" { | 
					
						
							|  |  |  | 		if hip.objPrefix != "" { | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 			// Bucket is required if object-prefix is given
 | 
					
						
							|  |  |  | 			err = ErrHealMissingBucket | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2017-03-16 15:15:06 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	} else if isReservedOrInvalidBucket(hip.bucket, false) { | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 		err = ErrInvalidBucketName | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	// empty prefix is valid.
 | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	if !IsValidObjectPrefix(hip.objPrefix) { | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 		err = ErrInvalidObjectName | 
					
						
							| 
									
										
										
										
											2018-01-12 02:21:41 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	if len(qParms[string(mgmtClientToken)]) > 0 { | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 		hip.clientToken = qParms[string(mgmtClientToken)][0] | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	if _, ok := qParms[string(mgmtForceStart)]; ok { | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 		hip.forceStart = true | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 	if _, ok := qParms[string(mgmtForceStop)]; ok { | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 		hip.forceStop = true | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// 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 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	// ignore body if clientToken is provided
 | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	if hip.clientToken == "" { | 
					
						
							|  |  |  | 		jerr := json.NewDecoder(r).Decode(&hip.hs) | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 		if jerr != nil { | 
					
						
							| 
									
										
										
										
											2019-10-12 09:50:54 +08:00
										 |  |  | 			logger.LogIf(context.Background(), jerr, logger.Application) | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 			err = ErrRequestBodyParse | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-04-15 01:28:35 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	err = ErrNone | 
					
						
							|  |  |  | 	return | 
					
						
							| 
									
										
										
										
											2017-04-01 08:55:15 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // HealHandler - POST /minio/admin/v2/heal/
 | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | // -----------
 | 
					
						
							|  |  |  | // Start heal processing and return heal status items.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // On a successful heal sequence start, a unique client token is
 | 
					
						
							|  |  |  | // returned. Subsequent requests to this endpoint providing the client
 | 
					
						
							|  |  |  | // token will receive heal status records from the running heal
 | 
					
						
							|  |  |  | // sequence.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // If no client token is provided, and a heal sequence is in progress
 | 
					
						
							|  |  |  | // an error is returned with information about the running heal
 | 
					
						
							|  |  |  | // sequence. However, if the force-start flag is provided, the server
 | 
					
						
							|  |  |  | // aborts the running heal sequence and starts a new one.
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  | 	ctx := newContext(r, w, "Heal") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-12 02:21:41 +08:00
										 |  |  | 	// Check if this setup has an erasure coded backend.
 | 
					
						
							|  |  |  | 	if !globalIsXL { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrHealNotImplemented), r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-25 00:11:05 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	hip, errCode := extractHealInitParams(mux.Vars(r), r.URL.Query(), r.Body) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	if errCode != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 	type healResp struct { | 
					
						
							|  |  |  | 		respBytes []byte | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 		apiErr    APIError | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 		errBody   string | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Define a closure to start sending whitespace to client
 | 
					
						
							|  |  |  | 	// after 10s unless a response item comes in
 | 
					
						
							|  |  |  | 	keepConnLive := func(w http.ResponseWriter, respCh chan healResp) { | 
					
						
							|  |  |  | 		ticker := time.NewTicker(time.Second * 10) | 
					
						
							| 
									
										
										
										
											2018-05-05 01:43:20 +08:00
										 |  |  | 		defer ticker.Stop() | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 		started := false | 
					
						
							|  |  |  | 	forLoop: | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			select { | 
					
						
							|  |  |  | 			case <-ticker.C: | 
					
						
							|  |  |  | 				if !started { | 
					
						
							|  |  |  | 					// Start writing response to client
 | 
					
						
							|  |  |  | 					started = true | 
					
						
							|  |  |  | 					setCommonHeaders(w) | 
					
						
							| 
									
										
										
										
											2019-07-12 04:19:25 +08:00
										 |  |  | 					w.Header().Set(xhttp.ContentType, "text/event-stream") | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 					// Set 200 OK status
 | 
					
						
							|  |  |  | 					w.WriteHeader(200) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				// Send whitespace and keep connection open
 | 
					
						
							| 
									
										
										
										
											2019-07-12 04:19:25 +08:00
										 |  |  | 				w.Write([]byte(" ")) | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 				w.(http.Flusher).Flush() | 
					
						
							|  |  |  | 			case hr := <-respCh: | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 				switch hr.apiErr { | 
					
						
							|  |  |  | 				case noError: | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 					if started { | 
					
						
							|  |  |  | 						w.Write(hr.respBytes) | 
					
						
							|  |  |  | 						w.(http.Flusher).Flush() | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						writeSuccessResponseJSON(w, hr.respBytes) | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 				default: | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 					var errorRespJSON []byte | 
					
						
							|  |  |  | 					if hr.errBody == "" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 						errorRespJSON = encodeResponseJSON(getAPIErrorResponse(ctx, hr.apiErr, | 
					
						
							| 
									
										
										
										
											2019-07-03 13:34:32 +08:00
										 |  |  | 							r.URL.Path, w.Header().Get(xhttp.AmzRequestID), | 
					
						
							| 
									
										
										
										
											2019-07-02 03:22:01 +08:00
										 |  |  | 							globalDeploymentID)) | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 					} else { | 
					
						
							|  |  |  | 						errorRespJSON = encodeResponseJSON(APIErrorResponse{ | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 							Code:      hr.apiErr.Code, | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 							Message:   hr.errBody, | 
					
						
							|  |  |  | 							Resource:  r.URL.Path, | 
					
						
							| 
									
										
										
										
											2019-07-03 13:34:32 +08:00
										 |  |  | 							RequestID: w.Header().Get(xhttp.AmzRequestID), | 
					
						
							| 
									
										
										
										
											2019-07-02 03:22:01 +08:00
										 |  |  | 							HostID:    globalDeploymentID, | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 						}) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					if !started { | 
					
						
							|  |  |  | 						setCommonHeaders(w) | 
					
						
							| 
									
										
										
										
											2019-07-03 13:34:32 +08:00
										 |  |  | 						w.Header().Set(xhttp.ContentType, string(mimeJSON)) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 						w.WriteHeader(hr.apiErr.HTTPStatusCode) | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 					} | 
					
						
							|  |  |  | 					w.Write(errorRespJSON) | 
					
						
							|  |  |  | 					w.(http.Flusher).Flush() | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				break forLoop | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// find number of disks in the setup
 | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	info := objectAPI.StorageInfo(ctx) | 
					
						
							| 
									
										
										
										
											2019-10-23 12:01:14 +08:00
										 |  |  | 	numDisks := info.Backend.OfflineDisks.Sum() + info.Backend.OnlineDisks.Sum() | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	healPath := pathJoin(hip.bucket, hip.objPrefix) | 
					
						
							|  |  |  | 	if hip.clientToken == "" && !hip.forceStart && !hip.forceStop { | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 		nh, exists := globalAllHealState.getHealSequence(healPath) | 
					
						
							|  |  |  | 		if exists && !nh.hasEnded() && len(nh.currentStatus.Items) > 0 { | 
					
						
							|  |  |  | 			b, err := json.Marshal(madmin.HealStartSuccess{ | 
					
						
							|  |  |  | 				ClientToken:   nh.clientToken, | 
					
						
							|  |  |  | 				ClientAddress: nh.clientAddress, | 
					
						
							|  |  |  | 				StartTime:     nh.startTime, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 				writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Client token not specified but a heal sequence exists on a path,
 | 
					
						
							|  |  |  | 			// Send the token back to client.
 | 
					
						
							|  |  |  | 			writeSuccessResponseJSON(w, b) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	if hip.clientToken != "" && !hip.forceStart && !hip.forceStop { | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 		// Since clientToken is given, fetch heal status from running
 | 
					
						
							|  |  |  | 		// heal sequence.
 | 
					
						
							|  |  |  | 		respBytes, errCode := globalAllHealState.PopHealStatusJSON( | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 			healPath, hip.clientToken) | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 		if errCode != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 			writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) | 
					
						
							| 
									
										
										
										
											2018-01-23 06:54:55 +08:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			writeSuccessResponseJSON(w, respBytes) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 		return | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	respCh := make(chan healResp) | 
					
						
							|  |  |  | 	switch { | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	case hip.forceStop: | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 		go func() { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 			respBytes, apiErr := globalAllHealState.stopHealSequence(healPath) | 
					
						
							|  |  |  | 			hr := healResp{respBytes: respBytes, apiErr: apiErr} | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 			respCh <- hr | 
					
						
							|  |  |  | 		}() | 
					
						
							| 
									
										
										
										
											2019-08-30 04:53:27 +08:00
										 |  |  | 	case hip.clientToken == "": | 
					
						
							|  |  |  | 		nh := newHealSequence(hip.bucket, hip.objPrefix, handlers.GetSourceIP(r), numDisks, hip.hs, hip.forceStart) | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 		go func() { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 			respBytes, apiErr, errMsg := globalAllHealState.LaunchNewHealSequence(nh) | 
					
						
							|  |  |  | 			hr := healResp{respBytes, apiErr, errMsg} | 
					
						
							| 
									
										
										
										
											2018-11-05 11:24:16 +08:00
										 |  |  | 			respCh <- hr | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Due to the force-starting functionality, the Launch
 | 
					
						
							|  |  |  | 	// call above can take a long time - to keep the
 | 
					
						
							|  |  |  | 	// connection alive, we start sending whitespace
 | 
					
						
							|  |  |  | 	keepConnLive(w, respCh) | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-02-21 04:58:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-26 07:42:24 +08:00
										 |  |  | func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "HealBackgroundStatus") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction) | 
					
						
							| 
									
										
										
										
											2019-06-26 07:42:24 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if this setup has an erasure coded backend.
 | 
					
						
							|  |  |  | 	if !globalIsXL { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrHealNotImplemented), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var bgHealStates []madmin.BgHealState | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get local heal status first
 | 
					
						
							|  |  |  | 	bgHealStates = append(bgHealStates, getLocalBackgroundHealStatus()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if globalIsDistXL { | 
					
						
							|  |  |  | 		// Get heal status from other peers
 | 
					
						
							|  |  |  | 		peersHealStates := globalNotificationSys.BackgroundHealStatus() | 
					
						
							|  |  |  | 		bgHealStates = append(bgHealStates, peersHealStates...) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Aggregate healing result
 | 
					
						
							|  |  |  | 	var aggregatedHealStateResult = madmin.BgHealState{} | 
					
						
							|  |  |  | 	for _, state := range bgHealStates { | 
					
						
							|  |  |  | 		aggregatedHealStateResult.ScannedItemsCount += state.ScannedItemsCount | 
					
						
							|  |  |  | 		if aggregatedHealStateResult.LastHealActivity.Before(state.LastHealActivity) { | 
					
						
							|  |  |  | 			aggregatedHealStateResult.LastHealActivity = state.LastHealActivity | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := json.NewEncoder(w).Encode(aggregatedHealStateResult); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	w.(http.Flusher).Flush() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, action iampolicy.AdminAction) (ObjectLayer, auth.Credentials) { | 
					
						
							|  |  |  | 	var cred auth.Credentials | 
					
						
							|  |  |  | 	var adminAPIErr APIErrorCode | 
					
						
							| 
									
										
										
										
											2018-09-06 23:03:18 +08:00
										 |  |  | 	// Get current object layer instance.
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | 	objectAPI := newObjectLayerWithoutSafeModeFn() | 
					
						
							|  |  |  | 	if objectAPI == nil || globalNotificationSys == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 		return nil, cred | 
					
						
							| 
									
										
										
										
											2018-09-06 23:03:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "") | 
					
						
							| 
									
										
										
										
											2018-09-06 23:03:18 +08:00
										 |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 		return nil, cred | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	return objectAPI, cred | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | // AdminError - is a generic error for all admin APIs.
 | 
					
						
							|  |  |  | type AdminError struct { | 
					
						
							|  |  |  | 	Code       string | 
					
						
							|  |  |  | 	Message    string | 
					
						
							|  |  |  | 	StatusCode int | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ae AdminError) Error() string { | 
					
						
							|  |  |  | 	return ae.Message | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Admin API errors
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	AdminUpdateUnexpectedFailure = "XMinioAdminUpdateUnexpectedFailure" | 
					
						
							|  |  |  | 	AdminUpdateURLNotReachable   = "XMinioAdminUpdateURLNotReachable" | 
					
						
							|  |  |  | 	AdminUpdateApplyFailure      = "XMinioAdminUpdateApplyFailure" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | // toAdminAPIErrCode - converts errXLWriteQuorum error to admin API
 | 
					
						
							|  |  |  | // specific error.
 | 
					
						
							| 
									
										
										
										
											2018-11-13 03:07:43 +08:00
										 |  |  | func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode { | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 	switch err { | 
					
						
							|  |  |  | 	case errXLWriteQuorum: | 
					
						
							|  |  |  | 		return ErrAdminConfigNoQuorum | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2018-11-13 03:07:43 +08:00
										 |  |  | 		return toAPIErrorCode(ctx, err) | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  | func toAdminAPIErr(ctx context.Context, err error) APIError { | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		return noError | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-10-30 15:04:39 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var apiErr APIError | 
					
						
							|  |  |  | 	switch e := err.(type) { | 
					
						
							|  |  |  | 	case config.Error: | 
					
						
							|  |  |  | 		apiErr = APIError{ | 
					
						
							|  |  |  | 			Code:           "XMinioConfigError", | 
					
						
							|  |  |  | 			Description:    e.Error(), | 
					
						
							|  |  |  | 			HTTPStatusCode: http.StatusBadRequest, | 
					
						
							| 
									
										
										
										
											2019-08-28 02:37:47 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-10-30 15:04:39 +08:00
										 |  |  | 	case AdminError: | 
					
						
							|  |  |  | 		apiErr = APIError{ | 
					
						
							|  |  |  | 			Code:           e.Code, | 
					
						
							|  |  |  | 			Description:    e.Message, | 
					
						
							|  |  |  | 			HTTPStatusCode: e.StatusCode, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | 		if err == errConfigNotFound { | 
					
						
							|  |  |  | 			apiErr = APIError{ | 
					
						
							|  |  |  | 				Code:           "XMinioConfigError", | 
					
						
							|  |  |  | 				Description:    err.Error(), | 
					
						
							|  |  |  | 				HTTPStatusCode: http.StatusNotFound, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			apiErr = errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err)) | 
					
						
							| 
									
										
										
										
											2018-12-19 06:39:21 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | 	return apiErr | 
					
						
							| 
									
										
										
										
											2018-10-10 05:00:01 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-01 02:08:39 +08:00
										 |  |  | // Returns true if the trace.Info should be traced,
 | 
					
						
							|  |  |  | // false if certain conditions are not met.
 | 
					
						
							|  |  |  | // - input entry is not of the type *trace.Info*
 | 
					
						
							|  |  |  | // - errOnly entries are to be traced, not status code 2xx, 3xx.
 | 
					
						
							|  |  |  | // - all entries to be traced, if not trace only S3 API requests.
 | 
					
						
							|  |  |  | func mustTrace(entry interface{}, trcAll, errOnly bool) bool { | 
					
						
							|  |  |  | 	trcInfo, ok := entry.(trace.Info) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-07 03:08:58 +08:00
										 |  |  | 	trace := trcAll || !hasPrefix(trcInfo.ReqInfo.Path, minioReservedBucketPath+SlashSeparator) | 
					
						
							| 
									
										
										
										
											2019-08-01 02:08:39 +08:00
										 |  |  | 	if errOnly { | 
					
						
							|  |  |  | 		return trace && trcInfo.RespInfo.StatusCode >= http.StatusBadRequest | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return trace | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // TraceHandler - POST /minio/admin/v2/trace
 | 
					
						
							| 
									
										
										
										
											2019-06-09 06:54:41 +08:00
										 |  |  | // ----------
 | 
					
						
							|  |  |  | // The handler sends http trace to the connected HTTP client.
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "HTTPTrace") | 
					
						
							|  |  |  | 	trcAll := r.URL.Query().Get("all") == "true" | 
					
						
							| 
									
										
										
										
											2019-07-20 08:38:26 +08:00
										 |  |  | 	trcErr := r.URL.Query().Get("err") == "true" | 
					
						
							| 
									
										
										
										
											2019-06-22 07:47:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	_, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.ListServerInfoAdminAction, "") | 
					
						
							| 
									
										
										
										
											2019-06-22 07:47:51 +08:00
										 |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 04:19:25 +08:00
										 |  |  | 	w.Header().Set(xhttp.ContentType, "text/event-stream") | 
					
						
							| 
									
										
										
										
											2019-06-09 06:54:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	doneCh := make(chan struct{}) | 
					
						
							|  |  |  | 	defer close(doneCh) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-27 13:41:12 +08:00
										 |  |  | 	// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
 | 
					
						
							|  |  |  | 	// Use buffered channel to take care of burst sends or slow w.Write()
 | 
					
						
							|  |  |  | 	traceCh := make(chan interface{}, 4000) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | 	peers := getRestClients(globalEndpoints) | 
					
						
							| 
									
										
										
										
											2019-08-01 02:08:39 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	globalHTTPTrace.Subscribe(traceCh, doneCh, func(entry interface{}) bool { | 
					
						
							|  |  |  | 		return mustTrace(entry, trcAll, trcErr) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2019-06-27 13:41:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for _, peer := range peers { | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | 		if peer == nil { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-07-20 08:38:26 +08:00
										 |  |  | 		peer.Trace(traceCh, doneCh, trcAll, trcErr) | 
					
						
							| 
									
										
										
										
											2019-06-27 13:41:12 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-01 02:08:39 +08:00
										 |  |  | 	keepAliveTicker := time.NewTicker(500 * time.Millisecond) | 
					
						
							|  |  |  | 	defer keepAliveTicker.Stop() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-27 13:41:12 +08:00
										 |  |  | 	enc := json.NewEncoder(w) | 
					
						
							| 
									
										
										
										
											2019-06-09 06:54:41 +08:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case entry := <-traceCh: | 
					
						
							| 
									
										
										
										
											2019-06-27 13:41:12 +08:00
										 |  |  | 			if err := enc.Encode(entry); err != nil { | 
					
						
							| 
									
										
										
										
											2019-06-09 06:54:41 +08:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			w.(http.Flusher).Flush() | 
					
						
							| 
									
										
										
										
											2019-08-01 02:08:39 +08:00
										 |  |  | 		case <-keepAliveTicker.C: | 
					
						
							|  |  |  | 			if _, err := w.Write([]byte(" ")); err != nil { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			w.(http.Flusher).Flush() | 
					
						
							| 
									
										
										
										
											2019-06-09 06:54:41 +08:00
										 |  |  | 		case <-GlobalServiceDoneCh: | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-09-04 02:10:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // The handler sends console logs to the connected HTTP client.
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "ConsoleLog") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) | 
					
						
							| 
									
										
										
										
											2019-09-04 02:10:48 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	node := r.URL.Query().Get("node") | 
					
						
							|  |  |  | 	// limit buffered console entries if client requested it.
 | 
					
						
							|  |  |  | 	limitStr := r.URL.Query().Get("limit") | 
					
						
							|  |  |  | 	limitLines, err := strconv.Atoi(limitStr) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		limitLines = 10 | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-10-12 09:50:54 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	logKind := r.URL.Query().Get("logType") | 
					
						
							|  |  |  | 	if logKind == "" { | 
					
						
							|  |  |  | 		logKind = string(logger.All) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	logKind = strings.ToUpper(logKind) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-04 02:10:48 +08:00
										 |  |  | 	// Avoid reusing tcp connection if read timeout is hit
 | 
					
						
							|  |  |  | 	// This is needed to make r.Context().Done() work as
 | 
					
						
							|  |  |  | 	// expected in case of read timeout
 | 
					
						
							|  |  |  | 	w.Header().Add("Connection", "close") | 
					
						
							|  |  |  | 	w.Header().Set(xhttp.ContentType, "text/event-stream") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	doneCh := make(chan struct{}) | 
					
						
							|  |  |  | 	defer close(doneCh) | 
					
						
							|  |  |  | 	logCh := make(chan interface{}, 4000) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | 	peers := getRestClients(globalEndpoints) | 
					
						
							| 
									
										
										
										
											2019-09-04 02:10:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-12 09:50:54 +08:00
										 |  |  | 	globalConsoleSys.Subscribe(logCh, doneCh, node, limitLines, logKind, nil) | 
					
						
							| 
									
										
										
										
											2019-09-04 02:10:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for _, peer := range peers { | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | 		if peer == nil { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-09-06 06:51:27 +08:00
										 |  |  | 		if node == "" || strings.EqualFold(peer.host.Name, node) { | 
					
						
							| 
									
										
										
										
											2019-09-04 02:10:48 +08:00
										 |  |  | 			peer.ConsoleLog(logCh, doneCh) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	enc := json.NewEncoder(w) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	keepAliveTicker := time.NewTicker(500 * time.Millisecond) | 
					
						
							|  |  |  | 	defer keepAliveTicker.Stop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case entry := <-logCh: | 
					
						
							|  |  |  | 			log := entry.(madmin.LogInfo) | 
					
						
							| 
									
										
										
										
											2019-10-12 09:50:54 +08:00
										 |  |  | 			if log.SendLog(node, logKind) { | 
					
						
							| 
									
										
										
										
											2019-09-04 02:10:48 +08:00
										 |  |  | 				if err := enc.Encode(log); err != nil { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				w.(http.Flusher).Flush() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case <-keepAliveTicker.C: | 
					
						
							|  |  |  | 			if _, err := w.Write([]byte(" ")); err != nil { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			w.(http.Flusher).Flush() | 
					
						
							|  |  |  | 		case <-GlobalServiceDoneCh: | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-09-05 04:19:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // KMSKeyStatusHandler - GET /minio/admin/v2/kms/key/status?key-id=<master-key-id>
 | 
					
						
							| 
									
										
										
										
											2019-09-05 04:19:44 +08:00
										 |  |  | func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "KMSKeyStatusHandler") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) | 
					
						
							| 
									
										
										
										
											2019-09-05 04:19:44 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if GlobalKMS == nil { | 
					
						
							|  |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	keyID := r.URL.Query().Get("key-id") | 
					
						
							|  |  |  | 	if keyID == "" { | 
					
						
							| 
									
										
										
										
											2019-10-08 13:47:56 +08:00
										 |  |  | 		keyID = GlobalKMS.KeyID() | 
					
						
							| 
									
										
										
										
											2019-09-05 04:19:44 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	var response = madmin.KMSKeyStatus{ | 
					
						
							|  |  |  | 		KeyID: keyID, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	kmsContext := crypto.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
 | 
					
						
							|  |  |  | 	// 1. Generate a new key using the KMS.
 | 
					
						
							|  |  |  | 	key, sealedKey, err := GlobalKMS.GenerateKey(keyID, kmsContext) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		response.EncryptionErr = err.Error() | 
					
						
							|  |  |  | 		resp, err := json.Marshal(response) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, resp) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 2. Check whether we can update / re-wrap the sealed key.
 | 
					
						
							|  |  |  | 	sealedKey, err = GlobalKMS.UpdateKey(keyID, sealedKey, kmsContext) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		response.UpdateErr = err.Error() | 
					
						
							|  |  |  | 		resp, err := json.Marshal(response) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, resp) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 3. Verify that we can indeed decrypt the (encrypted) key
 | 
					
						
							|  |  |  | 	decryptedKey, err := GlobalKMS.UnsealKey(keyID, sealedKey, kmsContext) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		response.DecryptionErr = err.Error() | 
					
						
							|  |  |  | 		resp, err := json.Marshal(response) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, resp) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 4. Compare generated key with decrypted key
 | 
					
						
							|  |  |  | 	if subtle.ConstantTimeCompare(key[:], decryptedKey[:]) != 1 { | 
					
						
							|  |  |  | 		response.DecryptionErr = "The generated and the decrypted data key do not match" | 
					
						
							|  |  |  | 		resp, err := json.Marshal(response) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, resp) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	resp, err := json.Marshal(response) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	writeSuccessResponseJSON(w, resp) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-10-03 22:48:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-10 01:27:23 +08:00
										 |  |  | // ServerHardwareInfoHandler - GET /minio/admin/v2/hardwareinfo?Type={hwType}
 | 
					
						
							| 
									
										
										
										
											2019-10-03 22:48:38 +08:00
										 |  |  | // ----------
 | 
					
						
							|  |  |  | // Get all hardware information based on input type
 | 
					
						
							|  |  |  | // Supported types = cpu
 | 
					
						
							|  |  |  | func (a adminAPIHandlers) ServerHardwareInfoHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	ctx := newContext(r, w, "HardwareInfo") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 18:03:18 +08:00
										 |  |  | 	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) | 
					
						
							| 
									
										
										
										
											2019-10-03 22:48:38 +08:00
										 |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  | 	hardware := vars[madmin.HARDWARE] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch madmin.HardwareType(hardware) { | 
					
						
							|  |  |  | 	case madmin.CPU: | 
					
						
							|  |  |  | 		// Get CPU hardware details from local server's cpu(s)
 | 
					
						
							|  |  |  | 		cpu := getLocalCPUInfo(globalEndpoints, r) | 
					
						
							|  |  |  | 		// Notify all other MinIO peers to report cpu hardware
 | 
					
						
							|  |  |  | 		cpus := globalNotificationSys.CPUInfo() | 
					
						
							|  |  |  | 		cpus = append(cpus, cpu) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Marshal API response
 | 
					
						
							|  |  |  | 		jsonBytes, err := json.Marshal(cpus) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Reply with cpu hardware information (across nodes in a
 | 
					
						
							|  |  |  | 		// distributed setup) as json.
 | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-17 19:09:50 +08:00
										 |  |  | 	case madmin.NETWORK: | 
					
						
							|  |  |  | 		// Get Network hardware details from local server's network(s)
 | 
					
						
							|  |  |  | 		network := getLocalNetworkInfo(globalEndpoints, r) | 
					
						
							|  |  |  | 		// Notify all other MinIO peers to report network hardware
 | 
					
						
							|  |  |  | 		networks := globalNotificationSys.NetworkInfo() | 
					
						
							|  |  |  | 		networks = append(networks, network) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Marshal API response
 | 
					
						
							|  |  |  | 		jsonBytes, err := json.Marshal(networks) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Reply with cpu network information (across nodes in a
 | 
					
						
							|  |  |  | 		// distributed setup) as json.
 | 
					
						
							|  |  |  | 		writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-03 22:48:38 +08:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2019-10-31 14:41:18 +08:00
										 |  |  | 		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL) | 
					
						
							| 
									
										
										
										
											2019-10-03 22:48:38 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } |