| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  |  * Minio Cloud Storage, (C) 2016, 2017 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 ( | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | 	"encoding/xml" | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | 	"io/ioutil" | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2017-11-01 02:54:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/minio/minio/pkg/auth" | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 	minioAdminOpHeader   = "X-Minio-Operation" | 
					
						
							|  |  |  | 	minioConfigTmpFormat = "config-%s.json" | 
					
						
							| 
									
										
										
										
											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 ( | 
					
						
							| 
									
										
										
										
											2017-03-16 15:15:06 +08:00
										 |  |  | 	mgmtBucket         mgmtQueryKey = "bucket" | 
					
						
							|  |  |  | 	mgmtObject         mgmtQueryKey = "object" | 
					
						
							|  |  |  | 	mgmtPrefix         mgmtQueryKey = "prefix" | 
					
						
							|  |  |  | 	mgmtLockDuration   mgmtQueryKey = "duration" | 
					
						
							|  |  |  | 	mgmtDelimiter      mgmtQueryKey = "delimiter" | 
					
						
							|  |  |  | 	mgmtMarker         mgmtQueryKey = "marker" | 
					
						
							|  |  |  | 	mgmtKeyMarker      mgmtQueryKey = "key-marker" | 
					
						
							|  |  |  | 	mgmtMaxKey         mgmtQueryKey = "max-key" | 
					
						
							|  |  |  | 	mgmtDryRun         mgmtQueryKey = "dry-run" | 
					
						
							|  |  |  | 	mgmtUploadIDMarker mgmtQueryKey = "upload-id-marker" | 
					
						
							|  |  |  | 	mgmtMaxUploads     mgmtQueryKey = "max-uploads" | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	mgmtUploadID       mgmtQueryKey = "upload-id" | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-24 00:56:06 +08:00
										 |  |  | // ServerVersion - server version
 | 
					
						
							|  |  |  | type ServerVersion struct { | 
					
						
							|  |  |  | 	Version  string `json:"version"` | 
					
						
							|  |  |  | 	CommitID string `json:"commitID"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServerStatus - contains the response of service status API
 | 
					
						
							|  |  |  | type ServerStatus struct { | 
					
						
							|  |  |  | 	ServerVersion ServerVersion `json:"serverVersion"` | 
					
						
							| 
									
										
										
										
											2017-02-08 16:13:02 +08:00
										 |  |  | 	Uptime        time.Duration `json:"uptime"` | 
					
						
							| 
									
										
										
										
											2017-01-24 00:56:06 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-21 10:49:48 +08:00
										 |  |  | // ServiceStatusHandler - GET /?service
 | 
					
						
							|  |  |  | // HTTP header x-minio-operation: status
 | 
					
						
							|  |  |  | // ----------
 | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | // Fetches server status information like total disk space available
 | 
					
						
							|  |  |  | // to use, online disks, offline disks and quorum threshold.
 | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | func (adminAPI adminAPIHandlers) ServiceStatusHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-24 00:56:06 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Fetch server version
 | 
					
						
							|  |  |  | 	serverVersion := ServerVersion{Version: Version, CommitID: CommitID} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-08 16:13:02 +08:00
										 |  |  | 	// Fetch uptimes from all peers. This may fail to due to lack
 | 
					
						
							|  |  |  | 	// of read-quorum availability.
 | 
					
						
							|  |  |  | 	uptime, err := getPeerUptimes(globalAdminPeers) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		errorIf(err, "Possibly failed to get uptime from majority of servers.") | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-24 00:56:06 +08:00
										 |  |  | 	// Create API response
 | 
					
						
							|  |  |  | 	serverStatus := ServerStatus{ | 
					
						
							|  |  |  | 		ServerVersion: serverVersion, | 
					
						
							| 
									
										
										
										
											2017-02-08 16:13:02 +08:00
										 |  |  | 		Uptime:        uptime, | 
					
						
							| 
									
										
										
										
											2017-01-24 00:56:06 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Marshal API response
 | 
					
						
							|  |  |  | 	jsonBytes, err := json.Marshal(serverStatus) | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, ErrInternalError, r.URL) | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 		errorIf(err, "Failed to marshal storage info into json.") | 
					
						
							| 
									
										
										
										
											2016-12-21 10:05:25 +08:00
										 |  |  | 		return | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	// Reply with storage information (across nodes in a
 | 
					
						
							|  |  |  | 	// distributed setup) as json.
 | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 	writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-21 10:49:48 +08:00
										 |  |  | // ServiceRestartHandler - POST /?service
 | 
					
						
							|  |  |  | // HTTP header x-minio-operation: restart
 | 
					
						
							|  |  |  | // ----------
 | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | // Restarts minio server gracefully. In a distributed setup,  restarts
 | 
					
						
							|  |  |  | // all the servers in the cluster.
 | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | func (adminAPI adminAPIHandlers) ServiceRestartHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 	// Reply to the client before restarting minio server.
 | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	writeSuccessResponseHeadersOnly(w) | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-16 14:26:15 +08:00
										 |  |  | 	sendServiceCmd(globalAdminPeers, serviceRestart) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | // setCredsReq request
 | 
					
						
							|  |  |  | type setCredsReq struct { | 
					
						
							|  |  |  | 	Username string `xml:"username"` | 
					
						
							|  |  |  | 	Password string `xml:"password"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServiceCredsHandler - POST /?service
 | 
					
						
							|  |  |  | // HTTP header x-minio-operation: creds
 | 
					
						
							|  |  |  | // ----------
 | 
					
						
							|  |  |  | // Update credentials in a minio server. In a distributed setup, update all the servers
 | 
					
						
							|  |  |  | // in the cluster.
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) ServiceCredentialsHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Authenticate request
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Avoid setting new credentials when they are already passed
 | 
					
						
							| 
									
										
										
										
											2017-01-27 08:51:51 +08:00
										 |  |  | 	// by the environment.
 | 
					
						
							|  |  |  | 	if globalIsEnvCreds { | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | 		writeErrorResponse(w, ErrMethodNotAllowed, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Load request body
 | 
					
						
							|  |  |  | 	inputData, err := ioutil.ReadAll(r.Body) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrInternalError, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Unmarshal request body
 | 
					
						
							|  |  |  | 	var req setCredsReq | 
					
						
							|  |  |  | 	err = xml.Unmarshal(inputData, &req) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		errorIf(err, "Cannot unmarshal credentials request") | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrMalformedXML, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-01 02:54:32 +08:00
										 |  |  | 	creds, err := auth.CreateCredentials(req.Username, req.Password) | 
					
						
							| 
									
										
										
										
											2017-02-08 04:51:43 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Notify all other Minio peers to update credentials
 | 
					
						
							| 
									
										
										
										
											2017-02-08 04:51:43 +08:00
										 |  |  | 	updateErrs := updateCredsOnPeers(creds) | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | 	for peer, err := range updateErrs { | 
					
						
							|  |  |  | 		errorIf(err, "Unable to update credentials on peer %s.", peer) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-08 04:51:43 +08:00
										 |  |  | 	// Update local credentials in memory.
 | 
					
						
							| 
									
										
										
										
											2017-11-30 05:12:47 +08:00
										 |  |  | 	prevCred := globalServerConfig.SetCredential(creds) | 
					
						
							| 
									
										
										
										
											2017-08-09 03:14:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Save credentials to config file
 | 
					
						
							| 
									
										
										
										
											2017-11-30 05:12:47 +08:00
										 |  |  | 	if err = globalServerConfig.Save(); err != nil { | 
					
						
							| 
									
										
										
										
											2017-08-09 03:14:32 +08:00
										 |  |  | 		// Save the current creds when failed to update.
 | 
					
						
							| 
									
										
										
										
											2017-11-30 05:12:47 +08:00
										 |  |  | 		globalServerConfig.SetCredential(prevCred) | 
					
						
							| 
									
										
										
										
											2017-08-09 03:14:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		errorIf(err, "Unable to update the config with new credentials.") | 
					
						
							| 
									
										
										
										
											2017-01-18 06:25:59 +08:00
										 |  |  | 		writeErrorResponse(w, ErrInternalError, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// At this stage, the operation is successful, return 200 OK
 | 
					
						
							|  |  |  | 	w.WriteHeader(http.StatusOK) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | // ServerProperties holds some server information such as, version, region
 | 
					
						
							|  |  |  | // uptime, etc..
 | 
					
						
							|  |  |  | type ServerProperties struct { | 
					
						
							|  |  |  | 	Uptime   time.Duration `json:"uptime"` | 
					
						
							|  |  |  | 	Version  string        `json:"version"` | 
					
						
							|  |  |  | 	CommitID string        `json:"commitID"` | 
					
						
							|  |  |  | 	Region   string        `json:"region"` | 
					
						
							|  |  |  | 	SQSARN   []string      `json:"sqsARN"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServerConnStats holds transferred bytes from/to the server
 | 
					
						
							|  |  |  | type ServerConnStats struct { | 
					
						
							|  |  |  | 	TotalInputBytes  uint64 `json:"transferred"` | 
					
						
							|  |  |  | 	TotalOutputBytes uint64 `json:"received"` | 
					
						
							|  |  |  | 	Throughput       uint64 `json:"throughput,omitempty"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | // ServerHTTPMethodStats holds total number of HTTP operations from/to the server,
 | 
					
						
							|  |  |  | // including the average duration the call was spent.
 | 
					
						
							|  |  |  | type ServerHTTPMethodStats struct { | 
					
						
							|  |  |  | 	Count       uint64 `json:"count"` | 
					
						
							|  |  |  | 	AvgDuration string `json:"avgDuration"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServerHTTPStats holds all type of http operations performed to/from the server
 | 
					
						
							|  |  |  | // including their average execution time.
 | 
					
						
							|  |  |  | type ServerHTTPStats struct { | 
					
						
							|  |  |  | 	TotalHEADStats     ServerHTTPMethodStats `json:"totalHEADs"` | 
					
						
							|  |  |  | 	SuccessHEADStats   ServerHTTPMethodStats `json:"successHEADs"` | 
					
						
							|  |  |  | 	TotalGETStats      ServerHTTPMethodStats `json:"totalGETs"` | 
					
						
							|  |  |  | 	SuccessGETStats    ServerHTTPMethodStats `json:"successGETs"` | 
					
						
							|  |  |  | 	TotalPUTStats      ServerHTTPMethodStats `json:"totalPUTs"` | 
					
						
							|  |  |  | 	SuccessPUTStats    ServerHTTPMethodStats `json:"successPUTs"` | 
					
						
							|  |  |  | 	TotalPOSTStats     ServerHTTPMethodStats `json:"totalPOSTs"` | 
					
						
							|  |  |  | 	SuccessPOSTStats   ServerHTTPMethodStats `json:"successPOSTs"` | 
					
						
							|  |  |  | 	TotalDELETEStats   ServerHTTPMethodStats `json:"totalDELETEs"` | 
					
						
							|  |  |  | 	SuccessDELETEStats ServerHTTPMethodStats `json:"successDELETEs"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | // ServerInfoData holds storage, connections and other
 | 
					
						
							|  |  |  | // information of a given server.
 | 
					
						
							|  |  |  | type ServerInfoData struct { | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 	StorageInfo StorageInfo      `json:"storage"` | 
					
						
							|  |  |  | 	ConnStats   ServerConnStats  `json:"network"` | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | 	HTTPStats   ServerHTTPStats  `json:"http"` | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 	Properties  ServerProperties `json:"server"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | // ServerInfoHandler - GET /?info
 | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | // ----------
 | 
					
						
							|  |  |  | // Get server information
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Authenticate request
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 	// Web service response
 | 
					
						
							|  |  |  | 	reply := make([]ServerInfo, len(globalAdminPeers)) | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 	var wg sync.WaitGroup | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 	// Gather server information for all nodes
 | 
					
						
							|  |  |  | 	for i, p := range globalAdminPeers { | 
					
						
							|  |  |  | 		wg.Add(1) | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 		// Gather information from a peer in a goroutine
 | 
					
						
							|  |  |  | 		go func(idx int, peer adminPeer) { | 
					
						
							|  |  |  | 			defer wg.Done() | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 			// Initialize server info at index
 | 
					
						
							|  |  |  | 			reply[idx] = ServerInfo{Addr: peer.addr} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			serverInfoData, err := peer.cmdRunner.ServerInfoData() | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				errorIf(err, "Unable to get server info from %s.", peer.addr) | 
					
						
							| 
									
										
										
										
											2017-05-15 22:28:47 +08:00
										 |  |  | 				reply[idx].Error = err.Error() | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			reply[idx].Data = &serverInfoData | 
					
						
							|  |  |  | 		}(i, p) | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 	wg.Wait() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 	// Marshal API response
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | 	jsonBytes, err := json.Marshal(reply) | 
					
						
							| 
									
										
										
										
											2017-02-16 02:45:45 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrInternalError, r.URL) | 
					
						
							|  |  |  | 		errorIf(err, "Failed to marshal storage info into json.") | 
					
						
							|  |  |  | 		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) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | // validateLockQueryParams - Validates query params for list/clear locks management APIs.
 | 
					
						
							|  |  |  | func validateLockQueryParams(vars url.Values) (string, string, time.Duration, APIErrorCode) { | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	bucket := vars.Get(string(mgmtBucket)) | 
					
						
							|  |  |  | 	prefix := vars.Get(string(mgmtPrefix)) | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	durationStr := vars.Get(string(mgmtLockDuration)) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// N B empty bucket name is invalid
 | 
					
						
							|  |  |  | 	if !IsValidBucketName(bucket) { | 
					
						
							|  |  |  | 		return "", "", time.Duration(0), ErrInvalidBucketName | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// empty prefix is valid.
 | 
					
						
							|  |  |  | 	if !IsValidObjectPrefix(prefix) { | 
					
						
							|  |  |  | 		return "", "", time.Duration(0), ErrInvalidObjectName | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If older-than parameter was empty then set it to 0s to list
 | 
					
						
							|  |  |  | 	// all locks older than now.
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	if durationStr == "" { | 
					
						
							|  |  |  | 		durationStr = "0s" | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	duration, err := time.ParseDuration(durationStr) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		errorIf(err, "Failed to parse duration passed as query value.") | 
					
						
							|  |  |  | 		return "", "", time.Duration(0), ErrInvalidDuration | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	return bucket, prefix, duration, ErrNone | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | // ListLocksHandler - GET /?lock&bucket=mybucket&prefix=myprefix&duration=duration
 | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | // - bucket is a mandatory query parameter
 | 
					
						
							|  |  |  | // - prefix and older-than are optional query parameters
 | 
					
						
							|  |  |  | // HTTP header x-minio-operation: list
 | 
					
						
							|  |  |  | // ---------
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | // Lists locks held on a given bucket, prefix and duration it was held for.
 | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | func (adminAPI adminAPIHandlers) ListLocksHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vars := r.URL.Query() | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	bucket, prefix, duration, adminAPIErr := validateLockQueryParams(vars) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Fetch lock information of locks matching bucket/prefix that
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	// are available for longer than duration.
 | 
					
						
							|  |  |  | 	volLocks, err := listPeerLocksInfo(globalAdminPeers, bucket, prefix, duration) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, ErrInternalError, r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 		errorIf(err, "Failed to fetch lock information from remote nodes.") | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Marshal list of locks as json.
 | 
					
						
							|  |  |  | 	jsonBytes, err := json.Marshal(volLocks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, ErrInternalError, r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 		errorIf(err, "Failed to marshal lock information into json.") | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reply with list of locks held on bucket, matching prefix
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	// held longer than duration supplied, as json.
 | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 	writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | // ClearLocksHandler - POST /?lock&bucket=mybucket&prefix=myprefix&duration=duration
 | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | // - bucket is a mandatory query parameter
 | 
					
						
							|  |  |  | // - prefix and older-than are optional query parameters
 | 
					
						
							|  |  |  | // HTTP header x-minio-operation: clear
 | 
					
						
							|  |  |  | // ---------
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | // Clear locks held on a given bucket, prefix and duration it was held for.
 | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | func (adminAPI adminAPIHandlers) ClearLocksHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vars := r.URL.Query() | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	bucket, prefix, duration, adminAPIErr := validateLockQueryParams(vars) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Fetch lock information of locks matching bucket/prefix that
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	// are held for longer than duration.
 | 
					
						
							|  |  |  | 	volLocks, err := listPeerLocksInfo(globalAdminPeers, bucket, prefix, duration) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, ErrInternalError, r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 		errorIf(err, "Failed to fetch lock information from remote nodes.") | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Marshal list of locks as json.
 | 
					
						
							|  |  |  | 	jsonBytes, err := json.Marshal(volLocks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 		writeErrorResponse(w, ErrInternalError, r.URL) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 		errorIf(err, "Failed to marshal lock information into json.") | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-02 03:17:30 +08:00
										 |  |  | 	// Remove lock matching bucket/prefix held longer than duration.
 | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	for _, volLock := range volLocks { | 
					
						
							|  |  |  | 		globalNSMutex.ForceUnlock(volLock.Bucket, volLock.Object) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | 	// Reply with list of locks cleared, as json.
 | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  | 	writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							| 
									
										
										
										
											2017-01-04 15:39:22 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-16 15:15:06 +08:00
										 |  |  | // ListUploadsHealHandler - similar to listObjectsHealHandler
 | 
					
						
							|  |  |  | // GET
 | 
					
						
							|  |  |  | // /?heal&bucket=mybucket&prefix=myprefix&key-marker=mymarker&upload-id-marker=myuploadid&delimiter=mydelimiter&max-uploads=1000
 | 
					
						
							|  |  |  | // - bucket is mandatory query parameter
 | 
					
						
							|  |  |  | // - rest are optional query parameters List upto maxKey objects that
 | 
					
						
							|  |  |  | // need healing in a given bucket matching the given prefix.
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) ListUploadsHealHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Get object layer instance.
 | 
					
						
							|  |  |  | 	objLayer := newObjectLayerFn() | 
					
						
							|  |  |  | 	if objLayer == nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrServerNotInitialized, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate query params.
 | 
					
						
							|  |  |  | 	vars := r.URL.Query() | 
					
						
							|  |  |  | 	bucket := vars.Get(string(mgmtBucket)) | 
					
						
							|  |  |  | 	prefix, keyMarker, uploadIDMarker, delimiter, maxUploads, _ := getBucketMultipartResources(r.URL.Query()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := checkListMultipartArgs(bucket, prefix, keyMarker, uploadIDMarker, delimiter, objLayer); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if maxUploads <= 0 || maxUploads > maxUploadsList { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrInvalidMaxUploads, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get the list objects to be healed.
 | 
					
						
							|  |  |  | 	listMultipartInfos, err := objLayer.ListUploadsHeal(bucket, prefix, | 
					
						
							|  |  |  | 		keyMarker, uploadIDMarker, delimiter, maxUploads) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listResponse := generateListMultipartUploadsResponse(bucket, listMultipartInfos) | 
					
						
							|  |  |  | 	// Write success response.
 | 
					
						
							|  |  |  | 	writeSuccessResponseXML(w, encodeResponse(listResponse)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // extractListObjectsHealQuery - Validates query params for heal objects list management API.
 | 
					
						
							|  |  |  | func extractListObjectsHealQuery(vars url.Values) (string, string, string, string, int, APIErrorCode) { | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	bucket := vars.Get(string(mgmtBucket)) | 
					
						
							|  |  |  | 	prefix := vars.Get(string(mgmtPrefix)) | 
					
						
							|  |  |  | 	marker := vars.Get(string(mgmtMarker)) | 
					
						
							|  |  |  | 	delimiter := vars.Get(string(mgmtDelimiter)) | 
					
						
							|  |  |  | 	maxKeyStr := vars.Get(string(mgmtMaxKey)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// N B empty bucket name is invalid
 | 
					
						
							|  |  |  | 	if !IsValidBucketName(bucket) { | 
					
						
							|  |  |  | 		return "", "", "", "", 0, ErrInvalidBucketName | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// empty prefix is valid.
 | 
					
						
							|  |  |  | 	if !IsValidObjectPrefix(prefix) { | 
					
						
							|  |  |  | 		return "", "", "", "", 0, ErrInvalidObjectName | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-16 15:15:06 +08:00
										 |  |  | 	// check if maxKey is a valid integer, if present.
 | 
					
						
							|  |  |  | 	var maxKey int | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 	if maxKeyStr != "" { | 
					
						
							|  |  |  | 		if maxKey, err = strconv.Atoi(maxKeyStr); err != nil { | 
					
						
							|  |  |  | 			return "", "", "", "", 0, ErrInvalidMaxKeys | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate prefix, marker, delimiter and maxKey.
 | 
					
						
							|  |  |  | 	apiErr := validateListObjectsArgs(prefix, marker, delimiter, maxKey) | 
					
						
							|  |  |  | 	if apiErr != ErrNone { | 
					
						
							|  |  |  | 		return "", "", "", "", 0, apiErr | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return bucket, prefix, marker, delimiter, maxKey, ErrNone | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ListObjectsHealHandler - GET /?heal&bucket=mybucket&prefix=myprefix&marker=mymarker&delimiter=&mydelimiter&maxKey=1000
 | 
					
						
							|  |  |  | // - bucket is mandatory query parameter
 | 
					
						
							|  |  |  | // - rest are optional query parameters
 | 
					
						
							|  |  |  | // List upto maxKey objects that need healing in a given bucket matching the given prefix.
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) ListObjectsHealHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Get object layer instance.
 | 
					
						
							|  |  |  | 	objLayer := newObjectLayerFn() | 
					
						
							|  |  |  | 	if objLayer == nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrServerNotInitialized, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate query params.
 | 
					
						
							|  |  |  | 	vars := r.URL.Query() | 
					
						
							| 
									
										
										
										
											2017-03-16 15:15:06 +08:00
										 |  |  | 	bucket, prefix, marker, delimiter, maxKey, adminAPIErr := extractListObjectsHealQuery(vars) | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get the list objects to be healed.
 | 
					
						
							|  |  |  | 	objectInfos, err := objLayer.ListObjectsHeal(bucket, prefix, marker, delimiter, maxKey) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listResponse := generateListObjectsV1Response(bucket, prefix, marker, delimiter, maxKey, objectInfos) | 
					
						
							|  |  |  | 	// Write success response.
 | 
					
						
							|  |  |  | 	writeSuccessResponseXML(w, encodeResponse(listResponse)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-20 01:34:18 +08:00
										 |  |  | // ListBucketsHealHandler - GET /?heal
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) ListBucketsHealHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Get object layer instance.
 | 
					
						
							|  |  |  | 	objLayer := newObjectLayerFn() | 
					
						
							|  |  |  | 	if objLayer == nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrServerNotInitialized, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get the list buckets to be healed.
 | 
					
						
							|  |  |  | 	bucketsInfo, err := objLayer.ListBucketsHeal() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listResponse := generateListBucketsResponse(bucketsInfo) | 
					
						
							|  |  |  | 	// Write success response.
 | 
					
						
							|  |  |  | 	writeSuccessResponseXML(w, encodeResponse(listResponse)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-25 00:11:05 +08:00
										 |  |  | // HealBucketHandler - POST /?heal&bucket=mybucket&dry-run
 | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | // - x-minio-operation = bucket
 | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | // - bucket is mandatory query parameter
 | 
					
						
							|  |  |  | // Heal a given bucket, if present.
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) HealBucketHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Get object layer instance.
 | 
					
						
							|  |  |  | 	objLayer := newObjectLayerFn() | 
					
						
							|  |  |  | 	if objLayer == nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrServerNotInitialized, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate bucket name and check if it exists.
 | 
					
						
							|  |  |  | 	vars := r.URL.Query() | 
					
						
							|  |  |  | 	bucket := vars.Get(string(mgmtBucket)) | 
					
						
							|  |  |  | 	if err := checkBucketExist(bucket, objLayer); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-25 00:11:05 +08:00
										 |  |  | 	// if dry-run is present in query-params, then only perform validations and return success.
 | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	if isDryRun(vars) { | 
					
						
							|  |  |  | 		writeSuccessResponseHeadersOnly(w) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Heal the given bucket.
 | 
					
						
							|  |  |  | 	err := objLayer.HealBucket(bucket) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return 200 on success.
 | 
					
						
							|  |  |  | 	writeSuccessResponseHeadersOnly(w) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-25 00:11:05 +08:00
										 |  |  | // isDryRun - returns true if dry-run query param was set and false otherwise.
 | 
					
						
							|  |  |  | // otherwise.
 | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | func isDryRun(qval url.Values) bool { | 
					
						
							| 
									
										
										
										
											2017-01-25 00:11:05 +08:00
										 |  |  | 	if _, dryRun := qval[string(mgmtDryRun)]; dryRun { | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-15 01:28:35 +08:00
										 |  |  | // healResult - represents result of a heal operation like
 | 
					
						
							|  |  |  | // heal-object, heal-upload.
 | 
					
						
							|  |  |  | type healResult struct { | 
					
						
							|  |  |  | 	State healState `json:"state"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // healState - different states of heal operation
 | 
					
						
							|  |  |  | type healState int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	// healNone - none of the disks healed
 | 
					
						
							|  |  |  | 	healNone healState = iota | 
					
						
							|  |  |  | 	// healPartial - some disks were healed, others were offline
 | 
					
						
							|  |  |  | 	healPartial | 
					
						
							|  |  |  | 	// healOK - all disks were healed
 | 
					
						
							|  |  |  | 	healOK | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // newHealResult - returns healResult given number of disks healed and
 | 
					
						
							|  |  |  | // number of disks offline
 | 
					
						
							|  |  |  | func newHealResult(numHealedDisks, numOfflineDisks int) healResult { | 
					
						
							|  |  |  | 	var state healState | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case numHealedDisks == 0: | 
					
						
							|  |  |  | 		state = healNone | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case numOfflineDisks > 0: | 
					
						
							|  |  |  | 		state = healPartial | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		state = healOK | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return healResult{State: state} | 
					
						
							| 
									
										
										
										
											2017-04-01 08:55:15 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-25 00:11:05 +08:00
										 |  |  | // HealObjectHandler - POST /?heal&bucket=mybucket&object=myobject&dry-run
 | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | // - x-minio-operation = object
 | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | // - bucket and object are both mandatory query parameters
 | 
					
						
							|  |  |  | // Heal a given object, if present.
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) HealObjectHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Get object layer instance.
 | 
					
						
							|  |  |  | 	objLayer := newObjectLayerFn() | 
					
						
							|  |  |  | 	if objLayer == nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrServerNotInitialized, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vars := r.URL.Query() | 
					
						
							|  |  |  | 	bucket := vars.Get(string(mgmtBucket)) | 
					
						
							|  |  |  | 	object := vars.Get(string(mgmtObject)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate bucket and object names.
 | 
					
						
							|  |  |  | 	if err := checkBucketAndObjectNames(bucket, object); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if object exists.
 | 
					
						
							|  |  |  | 	if _, err := objLayer.GetObjectInfo(bucket, object); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-25 00:11:05 +08:00
										 |  |  | 	// if dry-run is set in query params then perform validations
 | 
					
						
							|  |  |  | 	// and return success.
 | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	if isDryRun(vars) { | 
					
						
							|  |  |  | 		writeSuccessResponseHeadersOnly(w) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-01 08:55:15 +08:00
										 |  |  | 	numOfflineDisks, numHealedDisks, err := objLayer.HealObject(bucket, object) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-15 01:28:35 +08:00
										 |  |  | 	jsonBytes, err := json.Marshal(newHealResult(numHealedDisks, numOfflineDisks)) | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return 200 on success.
 | 
					
						
							| 
									
										
										
										
											2017-04-01 08:55:15 +08:00
										 |  |  | 	writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							| 
									
										
										
										
											2017-01-18 02:02:58 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | // HealUploadHandler - POST /?heal&bucket=mybucket&object=myobject&upload-id=myuploadID&dry-run
 | 
					
						
							|  |  |  | // - x-minio-operation = upload
 | 
					
						
							|  |  |  | // - bucket, object and upload-id are mandatory query parameters
 | 
					
						
							|  |  |  | // Heal a given upload, if present.
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) HealUploadHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Get object layer instance.
 | 
					
						
							|  |  |  | 	objLayer := newObjectLayerFn() | 
					
						
							|  |  |  | 	if objLayer == nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrServerNotInitialized, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vars := r.URL.Query() | 
					
						
							|  |  |  | 	bucket := vars.Get(string(mgmtBucket)) | 
					
						
							|  |  |  | 	object := vars.Get(string(mgmtObject)) | 
					
						
							|  |  |  | 	uploadID := vars.Get(string(mgmtUploadID)) | 
					
						
							|  |  |  | 	uploadObj := path.Join(bucket, object, uploadID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate bucket and object names as supplied via query
 | 
					
						
							|  |  |  | 	// parameters.
 | 
					
						
							|  |  |  | 	if err := checkBucketAndObjectNames(bucket, object); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate the bucket and object w.r.t backend representation
 | 
					
						
							|  |  |  | 	// of an upload.
 | 
					
						
							|  |  |  | 	if err := checkBucketAndObjectNames(minioMetaMultipartBucket, | 
					
						
							|  |  |  | 		uploadObj); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if upload exists.
 | 
					
						
							|  |  |  | 	if _, err := objLayer.GetObjectInfo(minioMetaMultipartBucket, | 
					
						
							|  |  |  | 		uploadObj); err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// if dry-run is set in query params then perform validations
 | 
					
						
							|  |  |  | 	// and return success.
 | 
					
						
							|  |  |  | 	if isDryRun(vars) { | 
					
						
							|  |  |  | 		writeSuccessResponseHeadersOnly(w) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	//We are able to use HealObject for healing an upload since an
 | 
					
						
							|  |  |  | 	//ongoing upload has the same backend representation as an
 | 
					
						
							|  |  |  | 	//object.  The 'object' corresponding to a given bucket,
 | 
					
						
							|  |  |  | 	//object and uploadID is
 | 
					
						
							|  |  |  | 	//.minio.sys/multipart/bucket/object/uploadID.
 | 
					
						
							| 
									
										
										
										
											2017-04-01 08:55:15 +08:00
										 |  |  | 	numOfflineDisks, numHealedDisks, err := objLayer.HealObject(minioMetaMultipartBucket, uploadObj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-15 01:28:35 +08:00
										 |  |  | 	jsonBytes, err := json.Marshal(newHealResult(numHealedDisks, numOfflineDisks)) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return 200 on success.
 | 
					
						
							| 
									
										
										
										
											2017-04-01 08:55:15 +08:00
										 |  |  | 	writeSuccessResponseJSON(w, jsonBytes) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-25 00:11:05 +08:00
										 |  |  | // HealFormatHandler - POST /?heal&dry-run
 | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | // - x-minio-operation = format
 | 
					
						
							|  |  |  | // - bucket and object are both mandatory query parameters
 | 
					
						
							|  |  |  | // Heal a given object, if present.
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) HealFormatHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Get current object layer instance.
 | 
					
						
							|  |  |  | 	objectAPI := newObjectLayerFn() | 
					
						
							|  |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrServerNotInitialized, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if this setup is an erasure code backend, since
 | 
					
						
							|  |  |  | 	// heal-format is only applicable to single node XL and
 | 
					
						
							|  |  |  | 	// distributed XL setup.
 | 
					
						
							|  |  |  | 	if !globalIsXL { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrNotImplemented, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-25 00:11:05 +08:00
										 |  |  | 	// if dry-run is set in query-params, return success as
 | 
					
						
							|  |  |  | 	// validations are successful so far.
 | 
					
						
							|  |  |  | 	vars := r.URL.Query() | 
					
						
							|  |  |  | 	if isDryRun(vars) { | 
					
						
							|  |  |  | 		writeSuccessResponseHeadersOnly(w) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | 	// Create a new set of storage instances to heal format.json.
 | 
					
						
							|  |  |  | 	bootstrapDisks, err := initStorageDisks(globalEndpoints) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-27 00:52:23 +08:00
										 |  |  | 	// Wrap into retrying disks
 | 
					
						
							|  |  |  | 	retryingDisks := initRetryableStorageDisks(bootstrapDisks, | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:29 +08:00
										 |  |  | 		time.Millisecond, time.Millisecond*5, globalStorageHealthCheckInterval, globalStorageRetryThreshold) | 
					
						
							| 
									
										
										
										
											2017-10-27 00:52:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | 	// Heal format.json on available storage.
 | 
					
						
							| 
									
										
										
										
											2017-10-27 00:52:23 +08:00
										 |  |  | 	err = healFormatXL(retryingDisks) | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Instantiate new object layer with newly formatted storage.
 | 
					
						
							| 
									
										
										
										
											2017-10-27 00:52:23 +08:00
										 |  |  | 	newObjectAPI, err := newXLObjects(retryingDisks) | 
					
						
							| 
									
										
										
										
											2017-01-23 16:32:55 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Set object layer with newly formatted storage to globalObjectAPI.
 | 
					
						
							|  |  |  | 	globalObjLayerMutex.Lock() | 
					
						
							|  |  |  | 	globalObjectAPI = newObjectAPI | 
					
						
							|  |  |  | 	globalObjLayerMutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Shutdown storage belonging to old object layer instance.
 | 
					
						
							|  |  |  | 	objectAPI.Shutdown() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Inform peers to reinitialize storage with newly formatted storage.
 | 
					
						
							|  |  |  | 	reInitPeerDisks(globalAdminPeers) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return 200 on success.
 | 
					
						
							|  |  |  | 	writeSuccessResponseHeadersOnly(w) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-02-21 04:58:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // GetConfigHandler - GET /?config
 | 
					
						
							|  |  |  | // - x-minio-operation = get
 | 
					
						
							|  |  |  | // Get config.json of this minio setup.
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// check if objectLayer is initialized, if not return.
 | 
					
						
							|  |  |  | 	if newObjectLayerFn() == nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrServerNotInitialized, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get config.json from all nodes. In a single node setup, it
 | 
					
						
							|  |  |  | 	// returns local config.json.
 | 
					
						
							|  |  |  | 	configBytes, err := getPeerConfig(globalAdminPeers) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		errorIf(err, "Failed to get config from peers") | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 		writeErrorResponse(w, toAdminAPIErrCode(err), r.URL) | 
					
						
							| 
									
										
										
										
											2017-02-21 04:58:50 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	writeSuccessResponseJSON(w, configBytes) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // toAdminAPIErrCode - converts errXLWriteQuorum error to admin API
 | 
					
						
							|  |  |  | // specific error.
 | 
					
						
							|  |  |  | func toAdminAPIErrCode(err error) APIErrorCode { | 
					
						
							|  |  |  | 	switch err { | 
					
						
							|  |  |  | 	case errXLWriteQuorum: | 
					
						
							|  |  |  | 		return ErrAdminConfigNoQuorum | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return toAPIErrorCode(err) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SetConfigResult - represents detailed results of a set-config
 | 
					
						
							|  |  |  | // operation.
 | 
					
						
							|  |  |  | type nodeSummary struct { | 
					
						
							| 
									
										
										
										
											2017-03-03 18:53:48 +08:00
										 |  |  | 	Name   string `json:"name"` | 
					
						
							|  |  |  | 	ErrSet bool   `json:"errSet"` | 
					
						
							|  |  |  | 	ErrMsg string `json:"errMsg"` | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-03-03 18:53:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | type setConfigResult struct { | 
					
						
							|  |  |  | 	NodeResults []nodeSummary `json:"nodeResults"` | 
					
						
							|  |  |  | 	Status      bool          `json:"status"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // writeSetConfigResponse - writes setConfigResult value as json depending on the status.
 | 
					
						
							|  |  |  | func writeSetConfigResponse(w http.ResponseWriter, peers adminPeers, errs []error, status bool, reqURL *url.URL) { | 
					
						
							|  |  |  | 	var nodeResults []nodeSummary | 
					
						
							|  |  |  | 	// Build nodeResults based on error values received during
 | 
					
						
							|  |  |  | 	// set-config operation.
 | 
					
						
							|  |  |  | 	for i := range errs { | 
					
						
							|  |  |  | 		nodeResults = append(nodeResults, nodeSummary{ | 
					
						
							| 
									
										
										
										
											2017-03-03 18:53:48 +08:00
										 |  |  | 			Name:   peers[i].addr, | 
					
						
							|  |  |  | 			ErrSet: errs[i] != nil, | 
					
						
							|  |  |  | 			ErrMsg: fmt.Sprintf("%v", errs[i]), | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	result := setConfigResult{ | 
					
						
							|  |  |  | 		Status:      status, | 
					
						
							|  |  |  | 		NodeResults: nodeResults, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The following elaborate json encoding is to avoid escaping
 | 
					
						
							|  |  |  | 	// '<', '>' in <nil>. Note: json.Encoder.Encode() adds a
 | 
					
						
							|  |  |  | 	// gratuitous "\n".
 | 
					
						
							|  |  |  | 	var resultBuf bytes.Buffer | 
					
						
							|  |  |  | 	enc := json.NewEncoder(&resultBuf) | 
					
						
							|  |  |  | 	enc.SetEscapeHTML(false) | 
					
						
							|  |  |  | 	jsonErr := enc.Encode(result) | 
					
						
							|  |  |  | 	if jsonErr != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(jsonErr), reqURL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	writeSuccessResponseJSON(w, resultBuf.Bytes()) | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SetConfigHandler - PUT /?config
 | 
					
						
							|  |  |  | // - x-minio-operation = set
 | 
					
						
							|  |  |  | func (adminAPI adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	// Get current object layer instance.
 | 
					
						
							|  |  |  | 	objectAPI := newObjectLayerFn() | 
					
						
							|  |  |  | 	if objectAPI == nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrServerNotInitialized, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate request signature.
 | 
					
						
							|  |  |  | 	adminAPIErr := checkRequestAuthType(r, "", "", "") | 
					
						
							|  |  |  | 	if adminAPIErr != ErrNone { | 
					
						
							|  |  |  | 		writeErrorResponse(w, adminAPIErr, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Read configuration bytes from request body.
 | 
					
						
							|  |  |  | 	configBytes, err := ioutil.ReadAll(r.Body) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		errorIf(err, "Failed to read config from request body.") | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-30 05:12:47 +08:00
										 |  |  | 	var config serverConfig | 
					
						
							| 
									
										
										
										
											2017-09-08 02:16:13 +08:00
										 |  |  | 	err = json.Unmarshal(configBytes, &config) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		errorIf(err, "Failed to unmarshal config from request body.") | 
					
						
							|  |  |  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if globalIsEnvCreds { | 
					
						
							| 
									
										
										
										
											2017-11-30 05:12:47 +08:00
										 |  |  | 		creds := globalServerConfig.GetCredential() | 
					
						
							| 
									
										
										
										
											2017-09-08 02:16:13 +08:00
										 |  |  | 		if config.Credential.AccessKey != creds.AccessKey || | 
					
						
							|  |  |  | 			config.Credential.SecretKey != creds.SecretKey { | 
					
						
							|  |  |  | 			writeErrorResponse(w, ErrAdminCredentialsMismatch, r.URL) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 	// Write config received from request onto a temporary file on
 | 
					
						
							|  |  |  | 	// all nodes.
 | 
					
						
							|  |  |  | 	tmpFileName := fmt.Sprintf(minioConfigTmpFormat, mustGetUUID()) | 
					
						
							|  |  |  | 	errs := writeTmpConfigPeers(globalAdminPeers, tmpFileName, configBytes) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if the operation succeeded in quorum or more nodes.
 | 
					
						
							|  |  |  | 	rErr := reduceWriteQuorumErrs(errs, nil, len(globalAdminPeers)/2+1) | 
					
						
							|  |  |  | 	if rErr != nil { | 
					
						
							|  |  |  | 		writeSetConfigResponse(w, globalAdminPeers, errs, false, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Take a lock on minio/config.json. NB minio is a reserved
 | 
					
						
							|  |  |  | 	// bucket name and wouldn't conflict with normal object
 | 
					
						
							|  |  |  | 	// operations.
 | 
					
						
							| 
									
										
										
										
											2017-03-09 11:20:01 +08:00
										 |  |  | 	configLock := globalNSMutex.NewNSLock(minioReservedBucket, minioConfigFile) | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 	if configLock.GetLock(globalObjectTimeout) != nil { | 
					
						
							|  |  |  | 		writeErrorResponse(w, ErrOperationTimedOut, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-02-28 03:40:27 +08:00
										 |  |  | 	defer configLock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Rename the temporary config file to config.json
 | 
					
						
							|  |  |  | 	errs = commitConfigPeers(globalAdminPeers, tmpFileName) | 
					
						
							|  |  |  | 	rErr = reduceWriteQuorumErrs(errs, nil, len(globalAdminPeers)/2+1) | 
					
						
							|  |  |  | 	if rErr != nil { | 
					
						
							|  |  |  | 		writeSetConfigResponse(w, globalAdminPeers, errs, false, r.URL) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// serverMux (cmd/server-mux.go) implements graceful shutdown,
 | 
					
						
							|  |  |  | 	// where all listeners are closed and process restart/shutdown
 | 
					
						
							|  |  |  | 	// happens after 5s or completion of all ongoing http
 | 
					
						
							|  |  |  | 	// requests, whichever is earlier.
 | 
					
						
							|  |  |  | 	writeSetConfigResponse(w, globalAdminPeers, errs, true, r.URL) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Restart all node for the modified config to take effect.
 | 
					
						
							|  |  |  | 	sendServiceCmd(globalAdminPeers, serviceRestart) | 
					
						
							|  |  |  | } |