mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
				
	
	
		
			376 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
|  * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc.
 | |
|  *
 | |
|  * 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 (
 | |
| 	"encoding/json"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strconv"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	minioAdminOpHeader = "X-Minio-Operation"
 | |
| )
 | |
| 
 | |
| // Type-safe query params.
 | |
| type mgmtQueryKey string
 | |
| 
 | |
| // Only valid query params for list/clear locks management APIs.
 | |
| const (
 | |
| 	mgmtBucket    mgmtQueryKey = "bucket"
 | |
| 	mgmtObject    mgmtQueryKey = "object"
 | |
| 	mgmtPrefix    mgmtQueryKey = "prefix"
 | |
| 	mgmtOlderThan mgmtQueryKey = "older-than"
 | |
| 	mgmtDelimiter mgmtQueryKey = "delimiter"
 | |
| 	mgmtMarker    mgmtQueryKey = "marker"
 | |
| 	mgmtMaxKey    mgmtQueryKey = "max-key"
 | |
| 	mgmtDryRun    mgmtQueryKey = "dry-run"
 | |
| )
 | |
| 
 | |
| // ServiceStatusHandler - GET /?service
 | |
| // HTTP header x-minio-operation: status
 | |
| // ----------
 | |
| // Fetches server status information like total disk space available
 | |
| // to use, online disks, offline disks and quorum threshold.
 | |
| func (adminAPI adminAPIHandlers) ServiceStatusHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	adminAPIErr := checkRequestAuthType(r, "", "", "")
 | |
| 	if adminAPIErr != ErrNone {
 | |
| 		writeErrorResponse(w, adminAPIErr, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	storageInfo := newObjectLayerFn().StorageInfo()
 | |
| 	jsonBytes, err := json.Marshal(storageInfo)
 | |
| 	if err != nil {
 | |
| 		writeErrorResponse(w, ErrInternalError, r.URL)
 | |
| 		errorIf(err, "Failed to marshal storage info into json.")
 | |
| 		return
 | |
| 	}
 | |
| 	// Reply with storage information (across nodes in a
 | |
| 	// distributed setup) as json.
 | |
| 	writeSuccessResponseJSON(w, jsonBytes)
 | |
| }
 | |
| 
 | |
| // ServiceRestartHandler - POST /?service
 | |
| // HTTP header x-minio-operation: restart
 | |
| // ----------
 | |
| // Restarts minio server gracefully. In a distributed setup,  restarts
 | |
| // all the servers in the cluster.
 | |
| func (adminAPI adminAPIHandlers) ServiceRestartHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	adminAPIErr := checkRequestAuthType(r, "", "", "")
 | |
| 	if adminAPIErr != ErrNone {
 | |
| 		writeErrorResponse(w, adminAPIErr, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Reply to the client before restarting minio server.
 | |
| 	writeSuccessResponseHeadersOnly(w)
 | |
| 
 | |
| 	sendServiceCmd(globalAdminPeers, serviceRestart)
 | |
| }
 | |
| 
 | |
| // validateLockQueryParams - Validates query params for list/clear locks management APIs.
 | |
| func validateLockQueryParams(vars url.Values) (string, string, time.Duration, APIErrorCode) {
 | |
| 	bucket := vars.Get(string(mgmtBucket))
 | |
| 	prefix := vars.Get(string(mgmtPrefix))
 | |
| 	relTimeStr := vars.Get(string(mgmtOlderThan))
 | |
| 
 | |
| 	// 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.
 | |
| 	if relTimeStr == "" {
 | |
| 		relTimeStr = "0s"
 | |
| 	}
 | |
| 	relTime, err := time.ParseDuration(relTimeStr)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Failed to parse duration passed as query value.")
 | |
| 		return "", "", time.Duration(0), ErrInvalidDuration
 | |
| 	}
 | |
| 
 | |
| 	return bucket, prefix, relTime, ErrNone
 | |
| }
 | |
| 
 | |
| // ListLocksHandler - GET /?lock&bucket=mybucket&prefix=myprefix&older-than=rel_time
 | |
| // - bucket is a mandatory query parameter
 | |
| // - prefix and older-than are optional query parameters
 | |
| // HTTP header x-minio-operation: list
 | |
| // ---------
 | |
| // Lists locks held on a given bucket, prefix and relative time.
 | |
| func (adminAPI adminAPIHandlers) ListLocksHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	adminAPIErr := checkRequestAuthType(r, "", "", "")
 | |
| 	if adminAPIErr != ErrNone {
 | |
| 		writeErrorResponse(w, adminAPIErr, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	vars := r.URL.Query()
 | |
| 	bucket, prefix, relTime, adminAPIErr := validateLockQueryParams(vars)
 | |
| 	if adminAPIErr != ErrNone {
 | |
| 		writeErrorResponse(w, adminAPIErr, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Fetch lock information of locks matching bucket/prefix that
 | |
| 	// are available since relTime.
 | |
| 	volLocks, err := listPeerLocksInfo(globalAdminPeers, bucket, prefix, relTime)
 | |
| 	if err != nil {
 | |
| 		writeErrorResponse(w, ErrInternalError, r.URL)
 | |
| 		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 {
 | |
| 		writeErrorResponse(w, ErrInternalError, r.URL)
 | |
| 		errorIf(err, "Failed to marshal lock information into json.")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Reply with list of locks held on bucket, matching prefix
 | |
| 	// older than relTime supplied, as json.
 | |
| 	writeSuccessResponseJSON(w, jsonBytes)
 | |
| }
 | |
| 
 | |
| // ClearLocksHandler - POST /?lock&bucket=mybucket&prefix=myprefix&older-than=relTime
 | |
| // - bucket is a mandatory query parameter
 | |
| // - prefix and older-than are optional query parameters
 | |
| // HTTP header x-minio-operation: clear
 | |
| // ---------
 | |
| // Clear locks held on a given bucket, prefix and relative time.
 | |
| func (adminAPI adminAPIHandlers) ClearLocksHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	adminAPIErr := checkRequestAuthType(r, "", "", "")
 | |
| 	if adminAPIErr != ErrNone {
 | |
| 		writeErrorResponse(w, adminAPIErr, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	vars := r.URL.Query()
 | |
| 	bucket, prefix, relTime, adminAPIErr := validateLockQueryParams(vars)
 | |
| 	if adminAPIErr != ErrNone {
 | |
| 		writeErrorResponse(w, adminAPIErr, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Fetch lock information of locks matching bucket/prefix that
 | |
| 	// are available since relTime.
 | |
| 	volLocks, err := listPeerLocksInfo(globalAdminPeers, bucket, prefix, relTime)
 | |
| 	if err != nil {
 | |
| 		writeErrorResponse(w, ErrInternalError, r.URL)
 | |
| 		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 {
 | |
| 		writeErrorResponse(w, ErrInternalError, r.URL)
 | |
| 		errorIf(err, "Failed to marshal lock information into json.")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Remove lock matching bucket/prefix older than relTime.
 | |
| 	for _, volLock := range volLocks {
 | |
| 		globalNSMutex.ForceUnlock(volLock.Bucket, volLock.Object)
 | |
| 	}
 | |
| 
 | |
| 	// Reply with list of locks cleared, as json.
 | |
| 	writeSuccessResponseJSON(w, jsonBytes)
 | |
| }
 | |
| 
 | |
| // validateHealQueryParams - Validates query params for heal list management API.
 | |
| func validateHealQueryParams(vars url.Values) (string, string, string, string, int, APIErrorCode) {
 | |
| 	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
 | |
| 	}
 | |
| 
 | |
| 	// check if maxKey is a valid integer.
 | |
| 	maxKey, err := strconv.Atoi(maxKeyStr)
 | |
| 	if err != nil {
 | |
| 		return "", "", "", "", 0, ErrInvalidMaxKeys
 | |
| 	}
 | |
| 
 | |
| 	// 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()
 | |
| 	bucket, prefix, marker, delimiter, maxKey, adminAPIErr := validateHealQueryParams(vars)
 | |
| 	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))
 | |
| }
 | |
| 
 | |
| // HealBucketHandler - POST /?heal&bucket=mybucket
 | |
| // - 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
 | |
| 	}
 | |
| 
 | |
| 	// if dry-run=yes, then only perform validations and return success.
 | |
| 	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)
 | |
| }
 | |
| 
 | |
| // isDryRun - returns true if dry-run query param was set to yes and false otherwise.
 | |
| func isDryRun(qval url.Values) bool {
 | |
| 	if dryRun := qval.Get(string(mgmtDryRun)); dryRun == "yes" {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // HealObjectHandler - POST /?heal&bucket=mybucket&object=myobject
 | |
| // - 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
 | |
| 	}
 | |
| 
 | |
| 	// if dry-run=yes, then only perform validations and return success.
 | |
| 	if isDryRun(vars) {
 | |
| 		writeSuccessResponseHeadersOnly(w)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	err := objLayer.HealObject(bucket, object)
 | |
| 	if err != nil {
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Return 200 on success.
 | |
| 	writeSuccessResponseHeadersOnly(w)
 | |
| }
 |