mirror of
https://github.com/pgsty/minio.git
synced 2026-11-01 21:32:03 +03:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb18c82976 | |||
| 2ab0681c0c | |||
| 83d4c5763c | |||
| c6f86d35d3 | |||
| d65a2c6725 | |||
| 70136fb55b | |||
| d6dd98e597 | |||
| c38ada1a26 | |||
| 48bc3f1d53 | |||
| e211f6f52e | |||
| f13f421e84 | |||
| cd03bfb3cf | |||
| add1b6cb6b | |||
| 4f3c9d7677 | |||
| 2e19619e79 | |||
| de736f2864 |
+2
-10
@@ -27,15 +27,7 @@ matrix:
|
||||
- CGO_ENABLED=0
|
||||
- GO111MODULE=on
|
||||
- GOPROXY=https://proxy.golang.org
|
||||
# Enable build cache
|
||||
# https://restic.net/blog/2018-09-02/travis-build-cache
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/go-build
|
||||
- $HOME/gopath/pkg/mod
|
||||
- $HOME/go/pkg/mod
|
||||
|
||||
go: 1.12.5
|
||||
go: 1.12.x
|
||||
script:
|
||||
- make
|
||||
- diff -au <(gofmt -s -d cmd) <(printf "")
|
||||
@@ -54,7 +46,7 @@ matrix:
|
||||
- CGO_ENABLED=0
|
||||
- GO111MODULE=on
|
||||
- GOPROXY=https://proxy.golang.org
|
||||
go: 1.12.5
|
||||
go: 1.12.x
|
||||
script:
|
||||
- go build --ldflags="$(go run buildscripts/gen-ldflags.go)" -o %GOPATH%\bin\minio.exe
|
||||
- bash buildscripts/go-coverage.sh
|
||||
|
||||
+137
-67
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2016, 2017, 2018, 2019 MinIO, Inc.
|
||||
* MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,11 +21,14 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -66,107 +69,139 @@ const (
|
||||
mgmtForceStop = "forceStop"
|
||||
)
|
||||
|
||||
var (
|
||||
// This struct literal represents the Admin API version that
|
||||
// the server uses.
|
||||
adminAPIVersionInfo = madmin.AdminAPIVersionInfo{
|
||||
Version: "1",
|
||||
func updateServer(updateURL, sha256Hex string, latestReleaseTime time.Time) (us madmin.ServerUpdateStatus, err error) {
|
||||
minioMode := getMinioMode()
|
||||
// No inputs provided we should try to update using the default URL.
|
||||
if updateURL == "" && sha256Hex == "" && latestReleaseTime.IsZero() {
|
||||
var updateMsg string
|
||||
updateMsg, sha256Hex, _, latestReleaseTime, err = getUpdateInfo(updateTimeout, minioMode)
|
||||
if err != nil {
|
||||
return us, err
|
||||
}
|
||||
if updateMsg == "" {
|
||||
us.CurrentVersion = Version
|
||||
us.UpdatedVersion = Version
|
||||
return us, nil
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
updateURL = minioReleaseURL + "minio.exe"
|
||||
} else {
|
||||
updateURL = minioReleaseURL + "minio"
|
||||
}
|
||||
}
|
||||
)
|
||||
if err = doUpdate(updateURL, sha256Hex, minioMode); err != nil {
|
||||
return us, err
|
||||
}
|
||||
us.CurrentVersion = Version
|
||||
us.UpdatedVersion = latestReleaseTime.Format(minioReleaseTagTimeLayout)
|
||||
return us, nil
|
||||
}
|
||||
|
||||
// VersionHandler - GET /minio/admin/version
|
||||
// -----------
|
||||
// Returns Administration API version
|
||||
func (a adminAPIHandlers) VersionHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "Version")
|
||||
// ServerUpdateHandler - POST /minio/admin/v1/update?updateURL={updateURL}
|
||||
// ----------
|
||||
// updates all minio servers and restarts them gracefully.
|
||||
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ServerUpdate")
|
||||
|
||||
objectAPI := validateAdminReq(ctx, w, r)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(adminAPIVersionInfo)
|
||||
if globalInplaceUpdateDisabled {
|
||||
// if MINIO_UPDATE=off - inplace update is disabled, mostly
|
||||
// in containers.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
updateURL := vars[peerRESTUpdateURL]
|
||||
mode := getMinioMode()
|
||||
var sha256Hex string
|
||||
var latestReleaseTime time.Time
|
||||
if updateURL != "" {
|
||||
u, err := url.Parse(updateURL)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := downloadReleaseURL(updateURL, updateTimeout, mode)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
sha256Hex, latestReleaseTime, err = parseReleaseData(content)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
u.Path = path.Dir(u.Path) + "minio.exe"
|
||||
} else {
|
||||
u.Path = path.Dir(u.Path) + "minio"
|
||||
}
|
||||
|
||||
updateURL = u.String()
|
||||
}
|
||||
|
||||
for _, nerr := range globalNotificationSys.ServerUpdate(updateURL, sha256Hex, latestReleaseTime) {
|
||||
if nerr.Err != nil {
|
||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||
logger.LogIf(ctx, nerr.Err)
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus, err := updateServer(updateURL, sha256Hex, latestReleaseTime)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
}
|
||||
|
||||
// ServiceStatusHandler - GET /minio/admin/v1/service
|
||||
// ----------
|
||||
// Returns server version and uptime.
|
||||
func (a adminAPIHandlers) ServiceStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ServiceStatus")
|
||||
|
||||
objectAPI := validateAdminReq(ctx, w, r)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch server version
|
||||
serverVersion := madmin.ServerVersion{
|
||||
Version: Version,
|
||||
CommitID: CommitID,
|
||||
}
|
||||
|
||||
// Fetch uptimes from all peers and pick the latest.
|
||||
uptime := getPeerUptimes(globalNotificationSys.ServerInfo(ctx))
|
||||
|
||||
// Create API response
|
||||
serverStatus := madmin.ServiceStatus{
|
||||
ServerVersion: serverVersion,
|
||||
Uptime: uptime,
|
||||
}
|
||||
|
||||
// Marshal API response
|
||||
jsonBytes, err := json.Marshal(serverStatus)
|
||||
jsonBytes, err := json.Marshal(updateStatus)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Reply with storage information (across nodes in a
|
||||
// distributed setup) as json.
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
|
||||
if updateStatus.CurrentVersion != updateStatus.UpdatedVersion {
|
||||
// We did upgrade - restart all services.
|
||||
globalServiceSignalCh <- serviceRestart
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceStopNRestartHandler - POST /minio/admin/v1/service
|
||||
// Body: {"action": <restart-action>}
|
||||
// ServiceActionHandler - POST /minio/admin/v1/service?action={action}
|
||||
// ----------
|
||||
// Restarts/Stops minio server gracefully. In a distributed setup,
|
||||
// restarts all the servers in the cluster.
|
||||
func (a adminAPIHandlers) ServiceStopNRestartHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ServiceStopNRestart")
|
||||
// restarts/stops minio server gracefully. In a distributed setup,
|
||||
func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ServiceAction")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
action := vars["action"]
|
||||
|
||||
objectAPI := validateAdminReq(ctx, w, r)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var sa madmin.ServiceAction
|
||||
err := json.NewDecoder(r.Body).Decode(&sa)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrRequestBodyParse), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var serviceSig serviceSignal
|
||||
switch sa.Action {
|
||||
case madmin.ServiceActionValueRestart:
|
||||
switch madmin.ServiceAction(action) {
|
||||
case madmin.ServiceActionRestart:
|
||||
serviceSig = serviceRestart
|
||||
case madmin.ServiceActionValueStop:
|
||||
case madmin.ServiceActionStop:
|
||||
serviceSig = serviceStop
|
||||
default:
|
||||
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action))
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
||||
logger.LogIf(ctx, errors.New("Invalid service action received"))
|
||||
return
|
||||
}
|
||||
|
||||
// Reply to the client before restarting minio server.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
|
||||
// Notify all other MinIO peers signal service.
|
||||
for _, nerr := range globalNotificationSys.SignalService(serviceSig) {
|
||||
if nerr.Err != nil {
|
||||
@@ -175,6 +210,9 @@ func (a adminAPIHandlers) ServiceStopNRestartHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
}
|
||||
|
||||
// Reply to the client before restarting, stopping MinIO server.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
|
||||
globalServiceSignalCh <- serviceSig
|
||||
}
|
||||
|
||||
@@ -989,6 +1027,24 @@ func (a adminAPIHandlers) GetConfigKeysHandler(w http.ResponseWriter, r *http.Re
|
||||
writeSuccessResponseJSON(w, []byte(econfigData))
|
||||
}
|
||||
|
||||
// AdminError - is a generic error for all admin APIs.
|
||||
type AdminError struct {
|
||||
Code string
|
||||
Message string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (ae AdminError) Error() string {
|
||||
return ae.Message
|
||||
}
|
||||
|
||||
// Admin API errors
|
||||
const (
|
||||
AdminUpdateUnexpectedFailure = "XMinioAdminUpdateUnexpectedFailure"
|
||||
AdminUpdateURLNotReachable = "XMinioAdminUpdateURLNotReachable"
|
||||
AdminUpdateApplyFailure = "XMinioAdminUpdateApplyFailure"
|
||||
)
|
||||
|
||||
// toAdminAPIErrCode - converts errXLWriteQuorum error to admin API
|
||||
// specific error.
|
||||
func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
||||
@@ -1001,7 +1057,21 @@ func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
||||
}
|
||||
|
||||
func toAdminAPIErr(ctx context.Context, err error) APIError {
|
||||
return errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err))
|
||||
if err == nil {
|
||||
return noError
|
||||
}
|
||||
apiErr := errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err))
|
||||
if apiErr.Code == "InternalError" {
|
||||
switch e := err.(type) {
|
||||
case AdminError:
|
||||
apiErr = APIError{
|
||||
Code: e.Code,
|
||||
Description: e.Message,
|
||||
HTTPStatusCode: e.StatusCode,
|
||||
}
|
||||
}
|
||||
}
|
||||
return apiErr
|
||||
}
|
||||
|
||||
// RemoveUser - DELETE /minio/admin/v1/remove-user?accessKey=<access_key>
|
||||
|
||||
+13
-123
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2016, 2017, 2018 MinIO, Inc.
|
||||
* MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -344,117 +344,35 @@ func initTestXLObjLayer() (ObjectLayer, []string, error) {
|
||||
return objLayer, xlDirs, nil
|
||||
}
|
||||
|
||||
func TestAdminVersionHandler(t *testing.T) {
|
||||
adminTestBed, err := prepareAdminXLTestBed()
|
||||
if err != nil {
|
||||
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
||||
}
|
||||
defer adminTestBed.TearDown()
|
||||
|
||||
req, err := newTestRequest("GET", "/minio/admin/version", 0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct request - %v", err)
|
||||
}
|
||||
cred := globalServerConfig.GetCredential()
|
||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to sign request - %v", err)
|
||||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
adminTestBed.router.ServeHTTP(rec, req)
|
||||
if http.StatusOK != rec.Code {
|
||||
t.Errorf("Unexpected status code - got %d but expected %d",
|
||||
rec.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
var result madmin.AdminAPIVersionInfo
|
||||
err = json.NewDecoder(rec.Body).Decode(&result)
|
||||
if err != nil {
|
||||
t.Errorf("json parse err: %v", err)
|
||||
}
|
||||
|
||||
if result != adminAPIVersionInfo {
|
||||
t.Errorf("unexpected version: %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
// cmdType - Represents different service subcomands like status, stop
|
||||
// and restart.
|
||||
type cmdType int
|
||||
|
||||
const (
|
||||
statusCmd cmdType = iota
|
||||
restartCmd
|
||||
restartCmd cmdType = iota
|
||||
stopCmd
|
||||
setCreds
|
||||
)
|
||||
|
||||
// String - String representation for cmdType
|
||||
func (c cmdType) String() string {
|
||||
switch c {
|
||||
case statusCmd:
|
||||
return "status"
|
||||
case restartCmd:
|
||||
return "restart"
|
||||
case stopCmd:
|
||||
return "stop"
|
||||
case setCreds:
|
||||
return "set-credentials"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// apiMethod - Returns the HTTP method corresponding to the admin REST
|
||||
// API for a given cmdType value.
|
||||
func (c cmdType) apiMethod() string {
|
||||
switch c {
|
||||
case statusCmd:
|
||||
return "GET"
|
||||
case restartCmd:
|
||||
return "POST"
|
||||
case stopCmd:
|
||||
return "POST"
|
||||
case setCreds:
|
||||
return "PUT"
|
||||
}
|
||||
return "GET"
|
||||
}
|
||||
|
||||
// apiEndpoint - Return endpoint for each admin REST API mapped to a
|
||||
// command here.
|
||||
func (c cmdType) apiEndpoint() string {
|
||||
switch c {
|
||||
case statusCmd, restartCmd, stopCmd:
|
||||
return "/minio/admin/v1/service"
|
||||
case setCreds:
|
||||
return "/minio/admin/v1/config/credential"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// toServiceSignal - Helper function that translates a given cmdType
|
||||
// value to its corresponding serviceSignal value.
|
||||
func (c cmdType) toServiceSignal() serviceSignal {
|
||||
switch c {
|
||||
case statusCmd:
|
||||
return serviceStatus
|
||||
case restartCmd:
|
||||
return serviceRestart
|
||||
case stopCmd:
|
||||
return serviceStop
|
||||
}
|
||||
return serviceStatus
|
||||
return serviceRestart
|
||||
}
|
||||
|
||||
func (c cmdType) toServiceActionValue() madmin.ServiceActionValue {
|
||||
func (c cmdType) toServiceAction() madmin.ServiceAction {
|
||||
switch c {
|
||||
case restartCmd:
|
||||
return madmin.ServiceActionValueRestart
|
||||
return madmin.ServiceActionRestart
|
||||
case stopCmd:
|
||||
return madmin.ServiceActionValueStop
|
||||
return madmin.ServiceActionStop
|
||||
}
|
||||
return madmin.ServiceActionValueStop
|
||||
return madmin.ServiceActionRestart
|
||||
}
|
||||
|
||||
// testServiceSignalReceiver - Helper function that simulates a
|
||||
@@ -469,19 +387,15 @@ func testServiceSignalReceiver(cmd cmdType, t *testing.T) {
|
||||
|
||||
// getServiceCmdRequest - Constructs a management REST API request for service
|
||||
// subcommands for a given cmdType value.
|
||||
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials, body []byte) (*http.Request, error) {
|
||||
req, err := newTestRequest(cmd.apiMethod(), cmd.apiEndpoint(), 0, nil)
|
||||
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials) (*http.Request, error) {
|
||||
queryVal := url.Values{}
|
||||
queryVal.Set("action", string(cmd.toServiceAction()))
|
||||
resource := "/minio/admin/v1/service?" + queryVal.Encode()
|
||||
req, err := newTestRequest(http.MethodPost, resource, 0, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set body
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
|
||||
// Set sha-sum header
|
||||
req.Header.Set("X-Amz-Content-Sha256", getSHA256Hash(body))
|
||||
|
||||
// management REST API uses signature V4 for authentication.
|
||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||
if err != nil {
|
||||
@@ -517,13 +431,7 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
||||
}
|
||||
credentials := globalServerConfig.GetCredential()
|
||||
|
||||
body, err := json.Marshal(madmin.ServiceAction{
|
||||
Action: cmd.toServiceActionValue()})
|
||||
if err != nil {
|
||||
t.Fatalf("JSONify error: %v", err)
|
||||
}
|
||||
|
||||
req, err := getServiceCmdRequest(cmd, credentials, body)
|
||||
req, err := getServiceCmdRequest(cmd, credentials)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build service status request %v", err)
|
||||
}
|
||||
@@ -531,19 +439,6 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
adminTestBed.router.ServeHTTP(rec, req)
|
||||
|
||||
if cmd == statusCmd {
|
||||
expectedInfo := madmin.ServiceStatus{
|
||||
ServerVersion: madmin.ServerVersion{Version: Version, CommitID: CommitID},
|
||||
}
|
||||
receivedInfo := madmin.ServiceStatus{}
|
||||
if jsonErr := json.Unmarshal(rec.Body.Bytes(), &receivedInfo); jsonErr != nil {
|
||||
t.Errorf("Failed to unmarshal StorageInfo - %v", jsonErr)
|
||||
}
|
||||
if expectedInfo.ServerVersion != receivedInfo.ServerVersion {
|
||||
t.Errorf("Expected storage info and received storage info differ, %v %v", expectedInfo, receivedInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
resp, _ := ioutil.ReadAll(rec.Body)
|
||||
t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
|
||||
@@ -554,11 +449,6 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Test for service status management REST API.
|
||||
func TestServiceStatusHandler(t *testing.T) {
|
||||
testServicesCmdHandler(statusCmd, t)
|
||||
}
|
||||
|
||||
// Test for service restart management REST API.
|
||||
func TestServiceRestartHandler(t *testing.T) {
|
||||
testServicesCmdHandler(restartCmd, t)
|
||||
|
||||
+4
-7
@@ -38,17 +38,14 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
||||
adminRouter := router.PathPrefix(adminAPIPathPrefix).Subrouter()
|
||||
|
||||
// Version handler
|
||||
adminRouter.Methods(http.MethodGet).Path("/version").HandlerFunc(httpTraceAll(adminAPI.VersionHandler))
|
||||
|
||||
adminV1Router := adminRouter.PathPrefix("/v1").Subrouter()
|
||||
|
||||
/// Service operations
|
||||
|
||||
// Service status
|
||||
adminV1Router.Methods(http.MethodGet).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceStatusHandler))
|
||||
|
||||
// Service restart and stop - TODO
|
||||
adminV1Router.Methods(http.MethodPost).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceStopNRestartHandler))
|
||||
// Restart and stop MinIO service.
|
||||
adminV1Router.Methods(http.MethodPost).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceActionHandler)).Queries("action", "{action:.*}")
|
||||
// Update MinIO servers.
|
||||
adminV1Router.Methods(http.MethodPost).Path("/update").HandlerFunc(httpTraceAll(adminAPI.ServerUpdateHandler)).Queries("updateURL", "{updateURL:.*}")
|
||||
|
||||
// Info operations
|
||||
adminV1Router.Methods(http.MethodGet).Path("/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler))
|
||||
|
||||
@@ -288,6 +288,9 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
||||
// Set delimiter value for "s3:delimiter" policy conditionals.
|
||||
r.Header.Set("delimiter", SlashSeparator)
|
||||
|
||||
// err will be nil here as we already called this function
|
||||
// earlier in this request.
|
||||
claims, _ := getClaimsFromToken(r)
|
||||
var newBucketsInfo []BucketInfo
|
||||
for _, bucketInfo := range bucketsInfo {
|
||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||
@@ -297,6 +300,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
||||
ConditionValues: getConditionValues(r, "", accessKey),
|
||||
IsOwner: owner,
|
||||
ObjectName: "",
|
||||
Claims: claims,
|
||||
}) {
|
||||
newBucketsInfo = append(newBucketsInfo, bucketInfo)
|
||||
}
|
||||
|
||||
+9
-1
@@ -67,7 +67,7 @@ func checkUpdate(mode string) {
|
||||
if globalInplaceUpdateDisabled {
|
||||
logger.StartupMessage(updateMsg)
|
||||
} else {
|
||||
logger.StartupMessage(prepareUpdateMessage("Run `minio update`", latestReleaseTime.Sub(currentReleaseTime)))
|
||||
logger.StartupMessage(prepareUpdateMessage("Run `mc admin update`", latestReleaseTime.Sub(currentReleaseTime)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,6 +359,14 @@ func handleCommonEnvVars() {
|
||||
globalCacheMaxUse = maxUse
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if cacheEncKey := os.Getenv("MINIO_CACHE_ENCRYPTION_MASTER_KEY"); cacheEncKey != "" {
|
||||
globalCacheKMSKeyID, globalCacheKMS, err = parseKMSMasterKey(cacheEncKey)
|
||||
if err != nil {
|
||||
logger.Fatal(uiErrInvalidCacheEncryptionKey(err), "Invalid cache encryption master key")
|
||||
}
|
||||
}
|
||||
// In place update is true by default if the MINIO_UPDATE is not set
|
||||
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then
|
||||
// in-place update is off.
|
||||
|
||||
@@ -19,6 +19,7 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -33,8 +34,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/djherbis/atime"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/disk"
|
||||
"github.com/minio/sio"
|
||||
"github.com/ncw/directio"
|
||||
)
|
||||
|
||||
@@ -45,6 +48,10 @@ const (
|
||||
cacheMetaVersion = "1.0.0"
|
||||
|
||||
cacheEnvDelimiter = ";"
|
||||
|
||||
// SSECacheEncrypted is the metadata key indicating that the object
|
||||
// is a cache entry encrypted with cache KMS master key in globalCacheKMS.
|
||||
SSECacheEncrypted = "X-Minio-Internal-Encrypted-Cache"
|
||||
)
|
||||
|
||||
// CacheChecksumInfoV1 - carries checksums of individual blocks on disk.
|
||||
@@ -223,6 +230,7 @@ func (c *diskCache) purge() {
|
||||
continue
|
||||
}
|
||||
cc := cacheControlOpts(objInfo)
|
||||
|
||||
if atime.Get(fi).Before(expiry) ||
|
||||
cc.isStale(objInfo.ModTime) {
|
||||
if err = removeAll(pathJoin(c.dir, obj.Name())); err != nil {
|
||||
@@ -273,6 +281,10 @@ func (c *diskCache) Stat(ctx context.Context, bucket, object string) (oi ObjectI
|
||||
}
|
||||
oi.Bucket = bucket
|
||||
oi.Name = object
|
||||
|
||||
if err = decryptCacheObjectETag(&oi); err != nil {
|
||||
return oi, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -323,7 +335,24 @@ func (c *diskCache) saveMetadata(ctx context.Context, bucket, object string, met
|
||||
|
||||
// Backend metadata could have changed through server side copy - reset cache metadata if that is the case
|
||||
func (c *diskCache) updateMetadataIfChanged(ctx context.Context, bucket, object string, bkObjectInfo, cacheObjInfo ObjectInfo) error {
|
||||
if !reflect.DeepEqual(bkObjectInfo.UserDefined, cacheObjInfo.UserDefined) ||
|
||||
|
||||
bkMeta := make(map[string]string)
|
||||
cacheMeta := make(map[string]string)
|
||||
for k, v := range bkObjectInfo.UserDefined {
|
||||
if hasPrefix(k, ReservedMetadataPrefix) {
|
||||
// Do not need to send any internal metadata
|
||||
continue
|
||||
}
|
||||
bkMeta[http.CanonicalHeaderKey(k)] = v
|
||||
}
|
||||
for k, v := range cacheObjInfo.UserDefined {
|
||||
if hasPrefix(k, ReservedMetadataPrefix) {
|
||||
// Do not need to send any internal metadata
|
||||
continue
|
||||
}
|
||||
cacheMeta[http.CanonicalHeaderKey(k)] = v
|
||||
}
|
||||
if !reflect.DeepEqual(bkMeta, cacheMeta) ||
|
||||
bkObjectInfo.ETag != cacheObjInfo.ETag ||
|
||||
bkObjectInfo.ContentType != cacheObjInfo.ContentType ||
|
||||
bkObjectInfo.Expires != cacheObjInfo.Expires {
|
||||
@@ -337,11 +366,11 @@ func getCacheSHADir(dir, bucket, object string) string {
|
||||
}
|
||||
|
||||
// Cache data to disk with bitrot checksum added for each block of 1MB
|
||||
func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, reader io.Reader, size int64) (int64, error) {
|
||||
func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, reader io.Reader, size uint64) (int64, error) {
|
||||
if err := os.MkdirAll(cachePath, 0777); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
bufSize := int64(readSizeV1)
|
||||
bufSize := uint64(readSizeV1)
|
||||
if size > 0 && bufSize > size {
|
||||
bufSize = size
|
||||
}
|
||||
@@ -396,6 +425,39 @@ func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, re
|
||||
return bytesWritten, nil
|
||||
}
|
||||
|
||||
func newCacheEncryptReader(content io.Reader, bucket, object string, metadata map[string]string) (r io.Reader, err error) {
|
||||
objectEncryptionKey, err := newCacheEncryptMetadata(bucket, object, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20})
|
||||
if err != nil {
|
||||
return nil, crypto.ErrInvalidCustomerKey
|
||||
}
|
||||
return reader, nil
|
||||
}
|
||||
func newCacheEncryptMetadata(bucket, object string, metadata map[string]string) ([]byte, error) {
|
||||
var sealedKey crypto.SealedKey
|
||||
if globalCacheKMS == nil {
|
||||
return nil, errKMSNotConfigured
|
||||
}
|
||||
key, encKey, err := globalCacheKMS.GenerateKey(globalCacheKMSKeyID, crypto.Context{bucket: path.Join(bucket, object)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objectKey := crypto.GenerateKey(key, rand.Reader)
|
||||
sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object)
|
||||
crypto.S3.CreateMetadata(metadata, globalCacheKMSKeyID, encKey, sealedKey)
|
||||
|
||||
if etag, ok := metadata["etag"]; ok {
|
||||
metadata["etag"] = hex.EncodeToString(objectKey.SealETag([]byte(etag)))
|
||||
}
|
||||
metadata[SSECacheEncrypted] = ""
|
||||
return objectKey[:], nil
|
||||
}
|
||||
|
||||
// Caches the object to disk
|
||||
func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Reader, size int64, opts ObjectOptions) error {
|
||||
if c.diskUsageHigh() {
|
||||
@@ -417,14 +479,29 @@ func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Read
|
||||
bufSize = size
|
||||
}
|
||||
|
||||
n, err := c.bitrotWriteToCache(ctx, cachePath, data, size)
|
||||
var metadata = make(map[string]string)
|
||||
for k, v := range opts.UserDefined {
|
||||
metadata[k] = v
|
||||
}
|
||||
var reader = data
|
||||
var actualSize = uint64(size)
|
||||
var err error
|
||||
if globalCacheKMS != nil {
|
||||
reader, err = newCacheEncryptReader(data, bucket, object, metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actualSize, _ = sio.EncryptedSize(uint64(size))
|
||||
}
|
||||
n, err := c.bitrotWriteToCache(ctx, cachePath, reader, actualSize)
|
||||
if IsErr(err, baseErrs...) {
|
||||
c.setOnline(false)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.saveMetadata(ctx, bucket, object, opts.UserDefined, n)
|
||||
|
||||
return c.saveMetadata(ctx, bucket, object, metadata, n)
|
||||
}
|
||||
|
||||
// checks streaming bitrot checksum of cached object before returning data
|
||||
@@ -484,7 +561,6 @@ func (c *diskCache) bitrotReadFromCache(ctx context.Context, filePath string, of
|
||||
if _, err := io.ReadFull(rc, checksumHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Reset()
|
||||
n, err := io.ReadFull(rc, *bufp)
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
@@ -527,7 +603,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
|
||||
var objInfo ObjectInfo
|
||||
cacheObjPath := getCacheSHADir(c.dir, bucket, object)
|
||||
|
||||
if objInfo, err = c.statCache(ctx, cacheObjPath); err != nil {
|
||||
if objInfo, err = c.Stat(ctx, bucket, object); err != nil {
|
||||
return nil, toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
@@ -552,7 +628,6 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
|
||||
// Cleanup function to cause the go routine above to exit, in
|
||||
// case of incomplete read.
|
||||
pipeCloser := func() { pr.Close() }
|
||||
|
||||
return fn(pr, h, opts.CheckCopyPrecondFn, pipeCloser)
|
||||
|
||||
}
|
||||
|
||||
+51
-1
@@ -17,8 +17,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -124,7 +126,7 @@ func backendDownError(err error) bool {
|
||||
|
||||
// IsCacheable returns if the object should be saved in the cache.
|
||||
func (o ObjectInfo) IsCacheable() bool {
|
||||
return !crypto.IsEncrypted(o.UserDefined)
|
||||
return !crypto.IsEncrypted(o.UserDefined) || globalCacheKMS != nil
|
||||
}
|
||||
|
||||
// reads file cached on disk from offset upto length
|
||||
@@ -147,6 +149,10 @@ func readCacheFileStream(filePath string, offset, length int64) (io.ReadCloser,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = os.Chtimes(filePath, time.Now(), st.ModTime()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify if its not a regular file, since subsequent Seek is undefined.
|
||||
if !st.Mode().IsRegular() {
|
||||
return nil, errIsNotRegular
|
||||
@@ -168,3 +174,47 @@ func readCacheFileStream(filePath string, offset, length int64) (io.ReadCloser,
|
||||
io.Closer
|
||||
}{Reader: io.LimitReader(fr, length), Closer: fr}, nil
|
||||
}
|
||||
|
||||
func isCacheEncrypted(meta map[string]string) bool {
|
||||
_, ok := meta[SSECacheEncrypted]
|
||||
return ok
|
||||
}
|
||||
|
||||
// decryptCacheObjectETag tries to decrypt the ETag saved in encrypted format using the cache KMS
|
||||
func decryptCacheObjectETag(info *ObjectInfo) error {
|
||||
// Directories are never encrypted.
|
||||
if info.IsDir {
|
||||
return nil
|
||||
}
|
||||
encrypted := crypto.S3.IsEncrypted(info.UserDefined) && isCacheEncrypted(info.UserDefined)
|
||||
|
||||
switch {
|
||||
case encrypted:
|
||||
if globalCacheKMS == nil {
|
||||
return errKMSNotConfigured
|
||||
}
|
||||
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(info.UserDefined)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
extKey, err := globalCacheKMS.UnsealKey(keyID, kmsKey, crypto.Context{info.Bucket: path.Join(info.Bucket, info.Name)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var objectKey crypto.ObjectKey
|
||||
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), info.Bucket, info.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
etagStr := tryDecryptETag(objectKey[:], info.ETag, false)
|
||||
// backend ETag was hex encoded before encrypting, so hex decode to get actual ETag
|
||||
etag, err := hex.DecodeString(etagStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.ETag = string(etag)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -263,7 +263,6 @@ func (c *cacheObjects) GetObjectInfo(ctx context.Context, bucket, object string,
|
||||
return cachedObjInfo, nil
|
||||
}
|
||||
}
|
||||
|
||||
objInfo, err := getObjectInfoFn(ctx, bucket, object, opts)
|
||||
if err != nil {
|
||||
if _, ok := err.(ObjectNotFound); ok {
|
||||
@@ -446,6 +445,7 @@ func checkAtimeSupport(dir string) (err error) {
|
||||
}
|
||||
func (c *cacheObjects) migrateCacheFromV1toV2(ctx context.Context) {
|
||||
logger.StartupMessage(colorBlue("Cache migration initiated ...."))
|
||||
|
||||
var wg = &sync.WaitGroup{}
|
||||
errs := make([]error, len(c.cache))
|
||||
for i, dc := range c.cache {
|
||||
|
||||
@@ -279,6 +279,23 @@ func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]st
|
||||
switch {
|
||||
default:
|
||||
return nil, errObjectTampered
|
||||
case crypto.S3.IsEncrypted(metadata) && isCacheEncrypted(metadata):
|
||||
if globalCacheKMS == nil {
|
||||
return nil, errKMSNotConfigured
|
||||
}
|
||||
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extKey, err := globalCacheKMS.UnsealKey(keyID, kmsKey, crypto.Context{bucket: path.Join(bucket, object)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var objectKey crypto.ObjectKey
|
||||
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return objectKey[:], nil
|
||||
case crypto.S3.IsEncrypted(metadata):
|
||||
if GlobalKMS == nil {
|
||||
return nil, errKMSNotConfigured
|
||||
|
||||
+29
-15
@@ -30,6 +30,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -362,7 +363,7 @@ func loadAndValidateCacheFormat(ctx context.Context, drives []string) (formats [
|
||||
|
||||
// reads cached object on disk and writes it back after adding bitrot
|
||||
// hashsum per block as per the new disk cache format.
|
||||
func migrateData(ctx context.Context, c *diskCache, oldfile, destDir string) error {
|
||||
func migrateCacheData(ctx context.Context, c *diskCache, bucket, object, oldfile, destDir string, metadata map[string]string) error {
|
||||
st, err := os.Stat(oldfile)
|
||||
if err != nil {
|
||||
err = osErrToFSFileErr(err)
|
||||
@@ -372,7 +373,18 @@ func migrateData(ctx context.Context, c *diskCache, oldfile, destDir string) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.bitrotWriteToCache(ctx, destDir, readCloser, st.Size())
|
||||
var reader io.Reader = readCloser
|
||||
|
||||
actualSize := uint64(st.Size())
|
||||
if globalCacheKMS != nil {
|
||||
reader, err = newCacheEncryptReader(readCloser, bucket, object, metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actualSize, _ = sio.EncryptedSize(uint64(st.Size()))
|
||||
}
|
||||
|
||||
_, err = c.bitrotWriteToCache(ctx, destDir, reader, uint64(actualSize))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -401,6 +413,7 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
|
||||
}
|
||||
|
||||
for _, bucket := range buckets {
|
||||
bucket = strings.TrimSuffix(bucket, SlashSeparator)
|
||||
var objMetaPaths []string
|
||||
root := path.Join(oldCacheBucketsPath, bucket)
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
@@ -422,10 +435,23 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
|
||||
return err
|
||||
}
|
||||
prevCachedPath := path.Join(c.dir, bucket, object)
|
||||
|
||||
// get old cached metadata
|
||||
oldMetaPath := pathJoin(oldCacheBucketsPath, bucket, object, cacheMetaJSONFile)
|
||||
metaPath := pathJoin(destdir, cacheMetaJSONFile)
|
||||
metaBytes, err := ioutil.ReadFile(oldMetaPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// marshal cache metadata after adding version and stat info
|
||||
meta := &cacheMeta{}
|
||||
if err = json.Unmarshal(metaBytes, &meta); err != nil {
|
||||
return err
|
||||
}
|
||||
// move cached object to new cache directory path
|
||||
// migrate cache data and add bit-rot protection hash sum
|
||||
// at the start of each block
|
||||
if err := migrateData(ctx, c, prevCachedPath, destdir); err != nil {
|
||||
if err := migrateCacheData(ctx, c, bucket, object, prevCachedPath, destdir, meta.Meta); err != nil {
|
||||
continue
|
||||
}
|
||||
stat, err := os.Stat(prevCachedPath)
|
||||
@@ -440,19 +466,7 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
|
||||
if err := os.Remove(prevCachedPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// move cached metadata after changing cache metadata version
|
||||
oldMetaPath := pathJoin(oldCacheBucketsPath, bucket, object, cacheMetaJSONFile)
|
||||
metaPath := pathJoin(destdir, cacheMetaJSONFile)
|
||||
metaBytes, err := ioutil.ReadFile(oldMetaPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// marshal cache metadata after adding version and stat info
|
||||
meta := &cacheMeta{}
|
||||
if err = json.Unmarshal(metaBytes, &meta); err != nil {
|
||||
return err
|
||||
}
|
||||
meta.Checksum = CacheChecksumInfoV1{Algorithm: HighwayHash256S.String(), Blocksize: cacheBlkSize}
|
||||
meta.Version = cacheMetaVersion
|
||||
meta.Stat.Size = stat.Size()
|
||||
|
||||
+29
-24
@@ -487,7 +487,6 @@ var notimplementedBucketResourceNames = map[string]bool{
|
||||
// List of not implemented object queries
|
||||
var notimplementedObjectResourceNames = map[string]bool{
|
||||
"acl": true,
|
||||
"policy": true,
|
||||
"restore": true,
|
||||
"tagging": true,
|
||||
"torrent": true,
|
||||
@@ -673,27 +672,30 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
||||
bucket, _ = urlPath2BucketObjectName(r.URL.Path)
|
||||
}
|
||||
}
|
||||
if bucket != "" {
|
||||
sr, err := globalDNSConfig.Get(bucket)
|
||||
if err != nil {
|
||||
if err == dns.ErrNoEntriesFound {
|
||||
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrNoSuchBucket), r.URL, guessIsBrowserReq(r))
|
||||
} else {
|
||||
writeErrorResponse(context.Background(), w, toAPIError(context.Background(), err), r.URL, guessIsBrowserReq(r))
|
||||
}
|
||||
return
|
||||
}
|
||||
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
|
||||
r.URL.Scheme = "http"
|
||||
if globalIsSSL {
|
||||
r.URL.Scheme = "https"
|
||||
}
|
||||
r.URL.Host = getHostFromSrv(sr)
|
||||
f.fwd.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if bucket == "" {
|
||||
f.handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
sr, err := globalDNSConfig.Get(bucket)
|
||||
if err != nil {
|
||||
if err == dns.ErrNoEntriesFound {
|
||||
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrNoSuchBucket),
|
||||
r.URL, guessIsBrowserReq(r))
|
||||
} else {
|
||||
writeErrorResponse(context.Background(), w, toAPIError(context.Background(), err),
|
||||
r.URL, guessIsBrowserReq(r))
|
||||
}
|
||||
return
|
||||
}
|
||||
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
|
||||
r.URL.Scheme = "http"
|
||||
if globalIsSSL {
|
||||
r.URL.Scheme = "https"
|
||||
}
|
||||
r.URL.Host = getHostFromSrv(sr)
|
||||
f.fwd.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
f.handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -715,9 +717,12 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
||||
// CopyObject requests should be handled at current endpoint as path style
|
||||
// requests have target bucket and object in URI and source details are in
|
||||
// header fields
|
||||
if r.Method == http.MethodPut && r.Header.Get("X-Amz-Copy-Source") != "" {
|
||||
f.handler.ServeHTTP(w, r)
|
||||
return
|
||||
if r.Method == http.MethodPut && r.Header.Get(xhttp.AmzCopySource) != "" {
|
||||
bucket, object = urlPath2BucketObjectName(r.Header.Get(xhttp.AmzCopySource))
|
||||
if bucket == "" || object == "" {
|
||||
f.handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
sr, err := globalDNSConfig.Get(bucket)
|
||||
if err != nil {
|
||||
|
||||
+4
-13
@@ -221,7 +221,10 @@ var (
|
||||
globalCacheExpiry = 90
|
||||
// Max allowed disk cache percentage
|
||||
globalCacheMaxUse = 80
|
||||
|
||||
// Disk cache KMS Key
|
||||
globalCacheKMSKeyID string
|
||||
// Initialized KMS configuration for disk cache
|
||||
globalCacheKMS crypto.KMS
|
||||
// Allocated etcd endpoint for config and bucket DNS.
|
||||
globalEtcdClient *etcd.Client
|
||||
|
||||
@@ -336,18 +339,6 @@ var (
|
||||
}
|
||||
return fmt.Sprintf
|
||||
}()
|
||||
colorGreenBold = func() func(format string, a ...interface{}) string {
|
||||
if isTerminal() {
|
||||
return color.New(color.FgGreen, color.Bold).SprintfFunc()
|
||||
}
|
||||
return fmt.Sprintf
|
||||
}()
|
||||
colorRedBold = func() func(format string, a ...interface{}) string {
|
||||
if isTerminal() {
|
||||
return color.New(color.FgRed, color.Bold).SprintfFunc()
|
||||
}
|
||||
return fmt.Sprintf
|
||||
}()
|
||||
)
|
||||
|
||||
// Returns minio global information, as a key value map.
|
||||
|
||||
+23
-4
@@ -43,6 +43,20 @@ type lockRESTClient struct {
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
func toLockError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch err.Error() {
|
||||
case errLockConflict.Error():
|
||||
return errLockConflict
|
||||
case errLockNotExpired.Error():
|
||||
return errLockNotExpired
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ServerAddr - dsync.NetLocker interface compatible method.
|
||||
func (client *lockRESTClient) ServerAddr() string {
|
||||
return client.serverURL.Host
|
||||
@@ -88,7 +102,6 @@ func (client *lockRESTClient) markHostDown() {
|
||||
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
|
||||
// after verifying format.json
|
||||
func (client *lockRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
|
||||
|
||||
if !client.isHostUp() {
|
||||
return nil, errors.New("Lock rest server node is down")
|
||||
}
|
||||
@@ -98,7 +111,6 @@ func (client *lockRESTClient) call(method string, values url.Values, body io.Rea
|
||||
}
|
||||
|
||||
respBody, err = client.restClient.Call(method, values, body, length)
|
||||
|
||||
if err == nil {
|
||||
return respBody, nil
|
||||
}
|
||||
@@ -107,7 +119,7 @@ func (client *lockRESTClient) call(method string, values url.Values, body io.Rea
|
||||
client.markHostDown()
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return nil, toLockError(err)
|
||||
}
|
||||
|
||||
// Stringer provides a canonicalized representation of node.
|
||||
@@ -138,7 +150,14 @@ func (client *lockRESTClient) restCall(call string, args dsync.LockArgs) (reply
|
||||
|
||||
respBody, err := client.call(call, values, nil, -1)
|
||||
defer http.DrainBody(respBody)
|
||||
return err == nil, err
|
||||
switch err {
|
||||
case nil:
|
||||
return true, nil
|
||||
case errLockConflict, errLockNotExpired:
|
||||
return false, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// RLock calls read lock REST API.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
@@ -53,6 +54,9 @@ type nameLockRequesterInfoPair struct {
|
||||
lri lockRequesterInfo
|
||||
}
|
||||
|
||||
var errLockConflict = errors.New("lock conflict")
|
||||
var errLockNotExpired = errors.New("lock not expired")
|
||||
|
||||
// Similar to removeEntry but only removes an entry only if the lock entry exists in map.
|
||||
func (l *localLocker) removeEntryIfExists(nlrip nameLockRequesterInfoPair) {
|
||||
// Check if entry is still in map (could have been removed altogether by 'concurrent' (R)Unlock of last entry)
|
||||
|
||||
@@ -35,7 +35,7 @@ func createLockTestServer(t *testing.T) (string, *lockRESTServer, string) {
|
||||
}
|
||||
|
||||
locker := &lockRESTServer{
|
||||
ll: localLocker{
|
||||
ll: &localLocker{
|
||||
mutex: sync.Mutex{},
|
||||
serviceEndpoint: "rpc-path",
|
||||
lockMap: make(map[string][]lockRequesterInfo),
|
||||
|
||||
+29
-13
@@ -42,7 +42,7 @@ const (
|
||||
|
||||
// To abstract a node over network.
|
||||
type lockRESTServer struct {
|
||||
ll localLocker
|
||||
ll *localLocker
|
||||
}
|
||||
|
||||
func (l *lockRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {
|
||||
@@ -62,6 +62,7 @@ func (l *lockRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool {
|
||||
func getLockArgs(r *http.Request) dsync.LockArgs {
|
||||
return dsync.LockArgs{
|
||||
UID: r.URL.Query().Get(lockRESTUID),
|
||||
Source: r.URL.Query().Get(lockRESTSource),
|
||||
Resource: r.URL.Query().Get(lockRESTResource),
|
||||
ServerAddr: r.URL.Query().Get(lockRESTServerAddr),
|
||||
ServiceEndpoint: r.URL.Query().Get(lockRESTServerEndpoint),
|
||||
@@ -75,7 +76,11 @@ func (l *lockRESTServer) LockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := l.ll.Lock(getLockArgs(r)); err != nil {
|
||||
success, err := l.ll.Lock(getLockArgs(r))
|
||||
if err == nil && !success {
|
||||
err = errLockConflict
|
||||
}
|
||||
if err != nil {
|
||||
l.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
@@ -88,7 +93,10 @@ func (l *lockRESTServer) UnlockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := l.ll.Unlock(getLockArgs(r)); err != nil {
|
||||
_, err := l.ll.Unlock(getLockArgs(r))
|
||||
// Ignore the Unlock() "reply" return value because if err == nil, "reply" is always true
|
||||
// Consequently, if err != nil, reply is always false
|
||||
if err != nil {
|
||||
l.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
@@ -101,7 +109,11 @@ func (l *lockRESTServer) RLockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := l.ll.RLock(getLockArgs(r)); err != nil {
|
||||
success, err := l.ll.RLock(getLockArgs(r))
|
||||
if err == nil && !success {
|
||||
err = errLockConflict
|
||||
}
|
||||
if err != nil {
|
||||
l.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
@@ -114,7 +126,10 @@ func (l *lockRESTServer) RUnlockHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := l.ll.RUnlock(getLockArgs(r)); err != nil {
|
||||
// Ignore the RUnlock() "reply" return value because if err == nil, "reply" is always true.
|
||||
// Consequently, if err != nil, reply is always false
|
||||
_, err := l.ll.RUnlock(getLockArgs(r))
|
||||
if err != nil {
|
||||
l.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
@@ -127,6 +142,8 @@ func (l *lockRESTServer) ForceUnlockHandler(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore the ForceUnlock() "reply" return value because if err == nil, "reply" is always true
|
||||
// Consequently, if err != nil, reply is always false
|
||||
if _, err := l.ll.ForceUnlock(getLockArgs(r)); err != nil {
|
||||
l.writeErrorResponse(w, err)
|
||||
return
|
||||
@@ -142,7 +159,6 @@ func (l *lockRESTServer) ExpiredHandler(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
lockArgs := getLockArgs(r)
|
||||
|
||||
success := true
|
||||
l.ll.mutex.Lock()
|
||||
defer l.ll.mutex.Unlock()
|
||||
// Lock found, proceed to verify if belongs to given uid.
|
||||
@@ -150,15 +166,11 @@ func (l *lockRESTServer) ExpiredHandler(w http.ResponseWriter, r *http.Request)
|
||||
// Check whether uid is still active
|
||||
for _, entry := range lri {
|
||||
if entry.UID == lockArgs.UID {
|
||||
success = false // When uid found, lock is still active so return not expired.
|
||||
break
|
||||
l.writeErrorResponse(w, errLockNotExpired)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
l.writeErrorResponse(w, errors.New("lock already expired"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// lockMaintenance loops over locks that have been active for some time and checks back
|
||||
@@ -189,11 +201,15 @@ func (l *lockRESTServer) lockMaintenance(interval time.Duration) {
|
||||
}
|
||||
|
||||
// Call back to original server verify whether the lock is still active (based on name & uid)
|
||||
expired, _ := c.Expired(dsync.LockArgs{
|
||||
expired, err := c.Expired(dsync.LockArgs{
|
||||
UID: nlrip.lri.UID,
|
||||
Resource: nlrip.name,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// For successful response, verify if lock was indeed active or stale.
|
||||
if expired {
|
||||
// The lock is no longer active at server that originated
|
||||
|
||||
@@ -116,7 +116,6 @@ func newApp(name string) *cli.App {
|
||||
// Register all commands.
|
||||
registerCommand(serverCmd)
|
||||
registerCommand(gatewayCmd)
|
||||
registerCommand(updateCmd)
|
||||
registerCommand(versionCmd)
|
||||
|
||||
// Set up app.
|
||||
|
||||
@@ -67,7 +67,7 @@ func newDsyncNodes(endpoints EndpointList) (clnts []dsync.NetLocker, myNode int)
|
||||
myNode = len(clnts)
|
||||
|
||||
receiver := &lockRESTServer{
|
||||
ll: localLocker{
|
||||
ll: &localLocker{
|
||||
serverAddr: endpoint.Host,
|
||||
serviceEndpoint: lockServicePath,
|
||||
lockMap: make(map[string][]lockRequesterInfo),
|
||||
@@ -75,7 +75,7 @@ func newDsyncNodes(endpoints EndpointList) (clnts []dsync.NetLocker, myNode int)
|
||||
}
|
||||
|
||||
globalLockServer = receiver
|
||||
locker = &(receiver.ll)
|
||||
locker = receiver.ll
|
||||
} else {
|
||||
host, err := xnet.ParseHost(endpoint.Host)
|
||||
logger.FatalIf(err, "Unable to parse Lock RPC Host")
|
||||
|
||||
@@ -405,6 +405,21 @@ func (sys *NotificationSys) DownloadProfilingData(ctx context.Context, writer io
|
||||
return profilingDataFound
|
||||
}
|
||||
|
||||
// ServerUpdate - updates remote peers.
|
||||
func (sys *NotificationSys) ServerUpdate(updateURL, sha256Hex string, latestReleaseTime time.Time) []NotificationPeerErr {
|
||||
ng := WithNPeers(len(sys.peerClients))
|
||||
for idx, client := range sys.peerClients {
|
||||
if client == nil {
|
||||
continue
|
||||
}
|
||||
client := client
|
||||
ng.Go(context.Background(), func() error {
|
||||
return client.ServerUpdate(updateURL, sha256Hex, latestReleaseTime)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
return ng.Wait()
|
||||
}
|
||||
|
||||
// SignalService - calls signal service RPC call on all peers.
|
||||
func (sys *NotificationSys) SignalService(sig serviceSignal) []NotificationPeerErr {
|
||||
ng := WithNPeers(len(sys.peerClients))
|
||||
|
||||
@@ -1063,7 +1063,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
r.Body = &detectDisconnect{r.Body, r.Context().Done()}
|
||||
|
||||
// X-Amz-Copy-Source shouldn't be set for this call.
|
||||
if _, ok := r.Header["X-Amz-Copy-Source"]; ok {
|
||||
if _, ok := r.Header[xhttp.AmzCopySource]; ok {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
@@ -1086,7 +1086,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
size := r.ContentLength
|
||||
rAuthType := getRequestAuthType(r)
|
||||
if rAuthType == authTypeStreamingSigned {
|
||||
if sizeStr, ok := r.Header["X-Amz-Decoded-Content-Length"]; ok {
|
||||
if sizeStr, ok := r.Header[xhttp.AmzDecodedContentLength]; ok {
|
||||
if sizeStr[0] == "" {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
+19
-1
@@ -503,10 +503,28 @@ func (client *peerRESTClient) LoadGroup(group string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerUpdate - sends server update message to remote peers.
|
||||
func (client *peerRESTClient) ServerUpdate(updateURL, sha256Hex string, latestReleaseTime time.Time) error {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTUpdateURL, updateURL)
|
||||
values.Set(peerRESTSha256Hex, sha256Hex)
|
||||
if !latestReleaseTime.IsZero() {
|
||||
values.Set(peerRESTLatestRelease, latestReleaseTime.Format(time.RFC3339))
|
||||
} else {
|
||||
values.Set(peerRESTLatestRelease, "")
|
||||
}
|
||||
respBody, err := client.call(peerRESTMethodServerUpdate, values, nil, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer http.DrainBody(respBody)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignalService - sends signal to peer nodes.
|
||||
func (client *peerRESTClient) SignalService(sig serviceSignal) error {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTSignal, string(sig))
|
||||
values.Set(peerRESTSignal, strconv.Itoa(int(sig)))
|
||||
respBody, err := client.call(peerRESTMethodSignalService, values, nil, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+17
-13
@@ -27,6 +27,7 @@ const (
|
||||
peerRESTMethodMemUsageInfo = "memusageinfo"
|
||||
peerRESTMethodDrivePerfInfo = "driveperfinfo"
|
||||
peerRESTMethodDeleteBucket = "deletebucket"
|
||||
peerRESTMethodServerUpdate = "serverupdate"
|
||||
peerRESTMethodSignalService = "signalservice"
|
||||
peerRESTMethodBackgroundHealStatus = "backgroundhealstatus"
|
||||
peerRESTMethodBackgroundOpsStatus = "backgroundopsstatus"
|
||||
@@ -53,17 +54,20 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
peerRESTNetPerfSize = "netperfsize"
|
||||
peerRESTBucket = "bucket"
|
||||
peerRESTUser = "user"
|
||||
peerRESTGroup = "group"
|
||||
peerRESTUserTemp = "user-temp"
|
||||
peerRESTPolicy = "policy"
|
||||
peerRESTUserOrGroup = "user-or-group"
|
||||
peerRESTIsGroup = "is-group"
|
||||
peerRESTSignal = "signal"
|
||||
peerRESTProfiler = "profiler"
|
||||
peerRESTDryRun = "dry-run"
|
||||
peerRESTTraceAll = "all"
|
||||
peerRESTTraceErr = "err"
|
||||
peerRESTNetPerfSize = "netperfsize"
|
||||
peerRESTBucket = "bucket"
|
||||
peerRESTUser = "user"
|
||||
peerRESTGroup = "group"
|
||||
peerRESTUserTemp = "user-temp"
|
||||
peerRESTPolicy = "policy"
|
||||
peerRESTUserOrGroup = "user-or-group"
|
||||
peerRESTIsGroup = "is-group"
|
||||
peerRESTUpdateURL = "updateURL"
|
||||
peerRESTSha256Hex = "sha256Hex"
|
||||
peerRESTLatestRelease = "latestReleaseTime"
|
||||
peerRESTSignal = "signal"
|
||||
peerRESTProfiler = "profiler"
|
||||
peerRESTDryRun = "dry-run"
|
||||
peerRESTTraceAll = "all"
|
||||
peerRESTTraceErr = "err"
|
||||
)
|
||||
|
||||
+39
-43
@@ -24,7 +24,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -67,46 +66,6 @@ func getServerInfo() (*ServerInfoData, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// uptimes - used to sort uptimes in chronological order.
|
||||
type uptimes []time.Duration
|
||||
|
||||
func (ts uptimes) Len() int {
|
||||
return len(ts)
|
||||
}
|
||||
|
||||
func (ts uptimes) Less(i, j int) bool {
|
||||
return ts[i] < ts[j]
|
||||
}
|
||||
|
||||
func (ts uptimes) Swap(i, j int) {
|
||||
ts[i], ts[j] = ts[j], ts[i]
|
||||
}
|
||||
|
||||
// getPeerUptimes - returns the uptime.
|
||||
func getPeerUptimes(serverInfo []ServerInfo) time.Duration {
|
||||
// In a single node Erasure or FS backend setup the uptime of
|
||||
// the setup is the uptime of the single minio server
|
||||
// instance.
|
||||
if !globalIsDistXL {
|
||||
return UTCNow().Sub(globalBootTime)
|
||||
}
|
||||
|
||||
var times []time.Duration
|
||||
|
||||
for _, info := range serverInfo {
|
||||
if info.Error != "" {
|
||||
continue
|
||||
}
|
||||
times = append(times, info.Data.Properties.Uptime)
|
||||
}
|
||||
|
||||
// Sort uptimes in chronological order.
|
||||
sort.Sort(uptimes(times))
|
||||
|
||||
// Return the latest time as the uptime.
|
||||
return times[0]
|
||||
}
|
||||
|
||||
// NetReadPerfInfoHandler - returns network read performance information.
|
||||
func (s *peerRESTServer) NetReadPerfInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
@@ -809,6 +768,35 @@ func (s *peerRESTServer) ListenBucketNotificationHandler(w http.ResponseWriter,
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
// ServerUpdateHandler - updates the current server.
|
||||
func (s *peerRESTServer) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
updateURL := vars[peerRESTUpdateURL]
|
||||
sha256Hex := vars[peerRESTSha256Hex]
|
||||
var latestReleaseTime time.Time
|
||||
var err error
|
||||
if latestRelease := vars[peerRESTLatestRelease]; latestRelease != "" {
|
||||
latestReleaseTime, err = time.Parse(latestRelease, time.RFC3339)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
us, err := updateServer(updateURL, sha256Hex, latestReleaseTime)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
if us.CurrentVersion != us.UpdatedVersion {
|
||||
globalServiceSignalCh <- serviceRestart
|
||||
}
|
||||
}
|
||||
|
||||
var errUnsupportedSignal = fmt.Errorf("unsupported signal: only restart and stop signals are supported")
|
||||
|
||||
// SignalServiceHandler - signal service handler.
|
||||
@@ -824,10 +812,17 @@ func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Req
|
||||
s.writeErrorResponse(w, errors.New("signal name is missing"))
|
||||
return
|
||||
}
|
||||
signal := serviceSignal(signalString)
|
||||
si, err := strconv.Atoi(signalString)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
signal := serviceSignal(si)
|
||||
defer w.(http.Flusher).Flush()
|
||||
switch signal {
|
||||
case serviceRestart, serviceStop:
|
||||
case serviceRestart:
|
||||
globalServiceSignalCh <- signal
|
||||
case serviceStop:
|
||||
globalServiceSignalCh <- signal
|
||||
default:
|
||||
s.writeErrorResponse(w, errUnsupportedSignal)
|
||||
@@ -935,6 +930,7 @@ func registerPeerRESTHandlers(router *mux.Router) {
|
||||
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDrivePerfInfo).HandlerFunc(httpTraceHdrs(server.DrivePerfInfoHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeleteBucket).HandlerFunc(httpTraceHdrs(server.DeleteBucketHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodSignalService).HandlerFunc(httpTraceHdrs(server.SignalServiceHandler)).Queries(restQueries(peerRESTSignal)...)
|
||||
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodServerUpdate).HandlerFunc(httpTraceHdrs(server.ServerUpdateHandler)).Queries(restQueries(peerRESTUpdateURL, peerRESTSha256Hex, peerRESTLatestRelease)...)
|
||||
|
||||
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBucketPolicyRemove).HandlerFunc(httpTraceAll(server.RemoveBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBucketPolicySet).HandlerFunc(httpTraceHdrs(server.SetBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ const (
|
||||
diskMinFreeSpace = 900 * humanize.MiByte // Min 900MiB free space.
|
||||
diskMinTotalSpace = diskMinFreeSpace // Min 900MiB total space.
|
||||
maxAllowedIOError = 5
|
||||
readBlockSize = humanize.KiByte * 32 // Default read block size 32KiB.
|
||||
readBlockSize = 4 * humanize.MiByte // Default read block size 4MiB.
|
||||
)
|
||||
|
||||
// isValidVolname verifies a volname name in accordance with object
|
||||
@@ -1230,7 +1230,7 @@ func (s *posix) CreateFile(volume, path string, fileSize int64, r io.Reader) (er
|
||||
bufp := s.pool.Get().(*[]byte)
|
||||
defer s.pool.Put(bufp)
|
||||
|
||||
written, err := xioutil.CopyAligned(w, r, *bufp)
|
||||
written, err := xioutil.CopyAligned(w, r, *bufp, fileSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -87,7 +87,6 @@ func runAllTests(suite *TestSuiteCommon, c *check) {
|
||||
suite.TestObjectGetAnonymous(c)
|
||||
suite.TestObjectGet(c)
|
||||
suite.TestMultipleObjects(c)
|
||||
suite.TestNotImplemented(c)
|
||||
suite.TestHeader(c)
|
||||
suite.TestPutBucket(c)
|
||||
suite.TestCopyObject(c)
|
||||
@@ -996,20 +995,6 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) {
|
||||
c.Assert(true, bytes.Equal(responseBody, []byte("hello three")))
|
||||
}
|
||||
|
||||
// TestNotImplemented - validates if object policy is implemented, should return 'NotImplemented'.
|
||||
func (s *TestSuiteCommon) TestNotImplemented(c *check) {
|
||||
// Generate a random bucket name.
|
||||
bucketName := getRandomBucketName()
|
||||
request, err := newTestSignedRequest("GET", s.endPoint+SlashSeparator+bucketName+"/object?policy",
|
||||
0, nil, s.accessKey, s.secretKey, s.signer)
|
||||
c.Assert(err, nil)
|
||||
|
||||
client := http.Client{Transport: s.transport}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, nil)
|
||||
c.Assert(response.StatusCode, http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// TestHeader - Validates the error response for an attempt to fetch non-existent object.
|
||||
func (s *TestSuiteCommon) TestHeader(c *check) {
|
||||
// generate a random bucket name.
|
||||
|
||||
+3
-4
@@ -23,12 +23,11 @@ import (
|
||||
)
|
||||
|
||||
// Type of service signals currently supported.
|
||||
type serviceSignal string
|
||||
type serviceSignal int
|
||||
|
||||
const (
|
||||
serviceStatus serviceSignal = "serviceStatus" // Gets status about the service.
|
||||
serviceRestart = "serviceRestart" // Restarts the service.
|
||||
serviceStop = "serviceStop" // Stops the server.
|
||||
serviceRestart serviceSignal = iota // Restarts the server.
|
||||
serviceStop // Stops the server.
|
||||
// Add new service requests here.
|
||||
)
|
||||
|
||||
|
||||
@@ -78,8 +78,6 @@ func handleSignals() {
|
||||
exit(stopProcess())
|
||||
case signal := <-globalServiceSignalCh:
|
||||
switch signal {
|
||||
case serviceStatus:
|
||||
// Ignore this at the moment.
|
||||
case serviceRestart:
|
||||
logger.Info("Restarting on service signal")
|
||||
stop := stopProcess()
|
||||
|
||||
@@ -71,6 +71,12 @@ var (
|
||||
"MINIO_CACHE_MAXUSE: Valid cache max-use value between 0-100",
|
||||
)
|
||||
|
||||
uiErrInvalidCacheEncryptionKey = newUIErrFn(
|
||||
"Invalid cache encryption master key value",
|
||||
"Please check the passed value",
|
||||
"MINIO_CACHE_ENCRYPTION_MASTER_KEY: For more information, please refer to https://docs.min.io/docs/minio-disk-cache-guide",
|
||||
)
|
||||
|
||||
uiErrInvalidCredentials = newUIErrFn(
|
||||
"Invalid credentials",
|
||||
"Please provide correct credentials",
|
||||
|
||||
+125
-133
@@ -23,6 +23,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -30,45 +31,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/inconshreveable/go-update"
|
||||
"github.com/minio/cli"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
_ "github.com/minio/sha256-simd" // Needed for sha256 hash verifier.
|
||||
)
|
||||
|
||||
// Check for new software updates.
|
||||
var updateCmd = cli.Command{
|
||||
Name: "update",
|
||||
Usage: "update minio to latest release",
|
||||
Action: mainUpdate,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "quiet",
|
||||
Usage: "disable any update prompt message",
|
||||
},
|
||||
},
|
||||
CustomHelpTemplate: `Name:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}}{{if .VisibleFlags}} [FLAGS]{{end}}
|
||||
{{if .VisibleFlags}}
|
||||
FLAGS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
EXIT STATUS:
|
||||
0 - You are already running the most recent version.
|
||||
1 - New update was applied successfully.
|
||||
-1 - Error in getting update information.
|
||||
|
||||
EXAMPLES:
|
||||
1. Check and update minio:
|
||||
{{.Prompt}} {{.HelpName}}
|
||||
`,
|
||||
}
|
||||
|
||||
const (
|
||||
minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z"
|
||||
minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH
|
||||
@@ -109,11 +77,14 @@ func releaseTimeToReleaseTag(releaseTime time.Time) string {
|
||||
|
||||
// releaseTagToReleaseTime - reverse of `releaseTimeToReleaseTag()`
|
||||
func releaseTagToReleaseTime(releaseTag string) (releaseTime time.Time, err error) {
|
||||
tagTimePart := strings.TrimPrefix(releaseTag, "RELEASE.")
|
||||
if tagTimePart == releaseTag {
|
||||
fields := strings.Split(releaseTag, ".")
|
||||
if len(fields) < 2 || len(fields) > 3 {
|
||||
return releaseTime, fmt.Errorf("%s is not a valid release tag", releaseTag)
|
||||
}
|
||||
return time.Parse(minioReleaseTagTimeLayout, tagTimePart)
|
||||
if fields[0] != "RELEASE" {
|
||||
return releaseTime, fmt.Errorf("%s is not a valid release tag", releaseTag)
|
||||
}
|
||||
return time.Parse(minioReleaseTagTimeLayout, fields[1])
|
||||
}
|
||||
|
||||
// getModTime - get the file modification time of `path`
|
||||
@@ -298,38 +269,58 @@ func getUserAgent(mode string) string {
|
||||
}
|
||||
|
||||
func downloadReleaseURL(releaseChecksumURL string, timeout time.Duration, mode string) (content string, err error) {
|
||||
req, err := http.NewRequest("GET", releaseChecksumURL, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, releaseChecksumURL, nil)
|
||||
if err != nil {
|
||||
return content, err
|
||||
return content, AdminError{
|
||||
Code: AdminUpdateUnexpectedFailure,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
req.Header.Set("User-Agent", getUserAgent(mode))
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: &http.Transport{
|
||||
// need to close connection after usage.
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: getUpdateTransport(timeout)}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return content, err
|
||||
if isNetworkOrHostDown(err) {
|
||||
return content, AdminError{
|
||||
Code: AdminUpdateURLNotReachable,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
}
|
||||
}
|
||||
return content, AdminError{
|
||||
Code: AdminUpdateUnexpectedFailure,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
if resp == nil {
|
||||
return content, fmt.Errorf("No response from server to download URL %s", releaseChecksumURL)
|
||||
return content, AdminError{
|
||||
Code: AdminUpdateUnexpectedFailure,
|
||||
Message: fmt.Sprintf("No response from server to download URL %s", releaseChecksumURL),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
defer xhttp.DrainBody(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return content, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status)
|
||||
return content, AdminError{
|
||||
Code: AdminUpdateUnexpectedFailure,
|
||||
Message: fmt.Sprintf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status),
|
||||
StatusCode: resp.StatusCode,
|
||||
}
|
||||
}
|
||||
contentBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return content, fmt.Errorf("Error reading response. %s", err)
|
||||
return content, AdminError{
|
||||
Code: AdminUpdateUnexpectedFailure,
|
||||
Message: fmt.Sprintf("Error reading response. %s", err),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
return string(contentBytes), err
|
||||
return string(contentBytes), nil
|
||||
}
|
||||
|
||||
// DownloadReleaseData - downloads release data from minio official server.
|
||||
@@ -338,6 +329,7 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e
|
||||
if runtime.GOOS == globalWindowsOSName {
|
||||
releaseURLs = minioReleaseWindowsInfoURLs
|
||||
}
|
||||
|
||||
return func() (data string, err error) {
|
||||
for _, url := range releaseURLs {
|
||||
data, err = downloadReleaseURL(url, timeout, mode)
|
||||
@@ -345,7 +337,7 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return data, fmt.Errorf("Failed to fetch release URL - last error: %s", err)
|
||||
return data, err
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -354,10 +346,20 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e
|
||||
//
|
||||
// The expected format is a single line with two words like:
|
||||
//
|
||||
// fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z
|
||||
// fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z.<hotfix_optional>
|
||||
//
|
||||
// The second word must be `minio.` appended to a standard release tag.
|
||||
func parseReleaseData(data string) (sha256Hex string, releaseTime time.Time, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = AdminError{
|
||||
Code: AdminUpdateUnexpectedFailure,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
fields := strings.Fields(data)
|
||||
if len(fields) != 2 {
|
||||
err = fmt.Errorf("Unknown release data `%s`", data)
|
||||
@@ -367,17 +369,18 @@ func parseReleaseData(data string) (sha256Hex string, releaseTime time.Time, err
|
||||
sha256Hex = fields[0]
|
||||
releaseInfo := fields[1]
|
||||
|
||||
fields = strings.SplitN(releaseInfo, ".", 2)
|
||||
if len(fields) != 2 {
|
||||
// Split release of style minio.RELEASE.2019-08-21T19-40-07Z.<hotfix>
|
||||
nfields := strings.SplitN(releaseInfo, ".", 2)
|
||||
if len(nfields) != 2 {
|
||||
err = fmt.Errorf("Unknown release information `%s`", releaseInfo)
|
||||
return sha256Hex, releaseTime, err
|
||||
}
|
||||
if fields[0] != "minio" {
|
||||
if nfields[0] != "minio" {
|
||||
err = fmt.Errorf("Unknown release `%s`", releaseInfo)
|
||||
return sha256Hex, releaseTime, err
|
||||
}
|
||||
|
||||
releaseTime, err = releaseTagToReleaseTime(fields[1])
|
||||
releaseTime, err = releaseTagToReleaseTime(nfields[1])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unknown release tag format. %s", err)
|
||||
}
|
||||
@@ -385,6 +388,27 @@ func parseReleaseData(data string) (sha256Hex string, releaseTime time.Time, err
|
||||
return sha256Hex, releaseTime, err
|
||||
}
|
||||
|
||||
const updateTimeout = 10 * time.Second
|
||||
|
||||
func getUpdateTransport(timeout time.Duration) http.RoundTripper {
|
||||
var updateTransport http.RoundTripper = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout,
|
||||
KeepAlive: timeout,
|
||||
DualStack: true,
|
||||
}
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
},
|
||||
IdleConnTimeout: timeout,
|
||||
TLSHandshakeTimeout: timeout,
|
||||
ExpectContinueTimeout: timeout,
|
||||
DisableCompression: true,
|
||||
}
|
||||
return updateTransport
|
||||
}
|
||||
|
||||
func getLatestReleaseTime(timeout time.Duration, mode string) (sha256Hex string, releaseTime time.Time, err error) {
|
||||
data, err := DownloadReleaseData(timeout, mode)
|
||||
if err != nil {
|
||||
@@ -450,21 +474,43 @@ func getUpdateInfo(timeout time.Duration, mode string) (updateMsg string, sha256
|
||||
return prepareUpdateMessage(downloadURL, older), sha256Hex, currentReleaseTime, latestReleaseTime, nil
|
||||
}
|
||||
|
||||
func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateStatusMsg string, err error) {
|
||||
if !ok {
|
||||
updateStatusMsg = colorRedBold("MinIO update to version RELEASE.%s canceled.",
|
||||
latestReleaseTime.Format(minioReleaseTagTimeLayout))
|
||||
return updateStatusMsg, nil
|
||||
}
|
||||
func doUpdate(updateURL, sha256Hex, mode string) (err error) {
|
||||
var sha256Sum []byte
|
||||
sha256Sum, err = hex.DecodeString(sha256Hex)
|
||||
if err != nil {
|
||||
return updateStatusMsg, err
|
||||
return AdminError{
|
||||
Code: AdminUpdateUnexpectedFailure,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := http.Get(getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime)))
|
||||
clnt := &http.Client{Transport: getUpdateTransport(30 * time.Second)}
|
||||
req, err := http.NewRequest(http.MethodGet, updateURL, nil)
|
||||
if err != nil {
|
||||
return updateStatusMsg, err
|
||||
return AdminError{
|
||||
Code: AdminUpdateUnexpectedFailure,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", getUserAgent(mode))
|
||||
|
||||
resp, err := clnt.Do(req)
|
||||
if err != nil {
|
||||
if isNetworkOrHostDown(err) {
|
||||
return AdminError{
|
||||
Code: AdminUpdateURLNotReachable,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
}
|
||||
}
|
||||
return AdminError{
|
||||
Code: AdminUpdateUnexpectedFailure,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
defer xhttp.DrainBody(resp.Body)
|
||||
|
||||
@@ -475,73 +521,19 @@ func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateSta
|
||||
Checksum: sha256Sum,
|
||||
},
|
||||
); err != nil {
|
||||
return updateStatusMsg, err
|
||||
}
|
||||
|
||||
return colorGreenBold("MinIO updated to version RELEASE.%s successfully.",
|
||||
latestReleaseTime.Format(minioReleaseTagTimeLayout)), nil
|
||||
}
|
||||
|
||||
// Confirm continues prompting until the input is boolean-ish.
|
||||
func confirm(prompt string, args ...interface{}) bool {
|
||||
for {
|
||||
var s string
|
||||
fmt.Fprintf(color.Output, prompt+": ", args...)
|
||||
fmt.Scanln(&s)
|
||||
switch s {
|
||||
case "Yes", "yes", "y", "Y":
|
||||
return true
|
||||
case "No", "no", "n", "N":
|
||||
return false
|
||||
if os.IsPermission(err) {
|
||||
return AdminError{
|
||||
Code: AdminUpdateApplyFailure,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
return AdminError{
|
||||
Code: AdminUpdateApplyFailure,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shouldUpdate(quiet bool, sha256Hex string, latestReleaseTime time.Time) (ok bool) {
|
||||
ok = true
|
||||
if !quiet {
|
||||
ok = confirm(colorGreenBold("Update to RELEASE.%s ? [%s]", latestReleaseTime.Format(minioReleaseTagTimeLayout), "y/n"))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func mainUpdate(ctx *cli.Context) {
|
||||
if len(ctx.Args()) != 0 {
|
||||
cli.ShowCommandHelpAndExit(ctx, "update", -1)
|
||||
}
|
||||
|
||||
handleCommonEnvVars()
|
||||
|
||||
quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
|
||||
if quiet {
|
||||
logger.EnableQuiet()
|
||||
}
|
||||
|
||||
minioMode := ""
|
||||
updateMsg, sha256Hex, _, latestReleaseTime, err := getUpdateInfo(10*time.Second, minioMode)
|
||||
if err != nil {
|
||||
logger.Info(err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// Nothing to update running the latest release.
|
||||
if updateMsg == "" {
|
||||
logger.Info(colorGreenBold("You are already running the most recent version of ‘minio’."))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
logger.Info(updateMsg)
|
||||
// if the in-place update is disabled then we shouldn't ask the
|
||||
// user to update the binaries.
|
||||
if strings.Contains(updateMsg, minioReleaseURL) && !globalInplaceUpdateDisabled {
|
||||
var updateStatusMsg string
|
||||
updateStatusMsg, err = doUpdate(sha256Hex, latestReleaseTime, shouldUpdate(quiet, sha256Hex, latestReleaseTime))
|
||||
if err != nil {
|
||||
logger.Info(colorRedBold("Unable to update ‘minio’."))
|
||||
logger.Info(err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
logger.Info(updateStatusMsg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -285,6 +285,7 @@ func TestParseReleaseData(t *testing.T) {
|
||||
{"more minio.RELEASE.fields", time.Time{}, "", fmt.Errorf(`Unknown release tag format. parsing time "fields" as "2006-01-02T15-04-05Z": cannot parse "fields" as "2006"`)},
|
||||
{"more minio.RELEASE.2016-10-07T01-16-39Z", releaseTime, "more", nil},
|
||||
{"fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z\n", releaseTime, "fbe246edbd382902db9a4035df7dce8cb441357d", nil},
|
||||
{"fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z.customer-hotfix\n", releaseTime, "fbe246edbd382902db9a4035df7dce8cb441357d", nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
+18
-2
@@ -29,6 +29,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@@ -446,10 +447,25 @@ func isNetworkOrHostDown(err error) bool {
|
||||
// or a non-temporary error.
|
||||
e, ok := err.(net.Error)
|
||||
if ok {
|
||||
return e.Timeout()
|
||||
urlErr, ok := e.(*url.Error)
|
||||
if ok {
|
||||
switch urlErr.Err.(type) {
|
||||
case *net.DNSError, *net.OpError, net.UnknownNetworkError:
|
||||
return true
|
||||
}
|
||||
}
|
||||
if e.Timeout() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
ok = false
|
||||
// Fallback to other mechanisms.
|
||||
if strings.Contains(err.Error(), "i/o timeout") {
|
||||
if strings.Contains(err.Error(), "Connection closed by foreign host") {
|
||||
ok = true
|
||||
} else if strings.Contains(err.Error(), "TLS handshake timeout") {
|
||||
// If error is - tlsHandshakeTimeoutError.
|
||||
ok = true
|
||||
} else if strings.Contains(err.Error(), "i/o timeout") {
|
||||
// If error is - tcp timeoutError.
|
||||
ok = true
|
||||
} else if strings.Contains(err.Error(), "connection timed out") {
|
||||
|
||||
@@ -1972,6 +1972,8 @@ func toWebAPIError(ctx context.Context, err error) APIError {
|
||||
return getAPIError(ErrStorageFull)
|
||||
case BucketNotFound:
|
||||
return getAPIError(ErrNoSuchBucket)
|
||||
case BucketNotEmpty:
|
||||
return getAPIError(ErrBucketNotEmpty)
|
||||
case BucketExists:
|
||||
return getAPIError(ErrBucketAlreadyOwnedByYou)
|
||||
case BucketNameInvalid:
|
||||
|
||||
@@ -338,7 +338,7 @@ func testDeleteBucketWebHandler(obj ObjectLayer, instanceType string, t TestErrH
|
||||
{"minio", false, "false token", "Authentication failed"},
|
||||
{"minio", false, token, "The specified bucket is not valid"},
|
||||
{bucketName, false, token, ""},
|
||||
{bucketName, true, token, "Bucket not empty"},
|
||||
{bucketName, true, token, "The bucket you tried to delete is not empty"},
|
||||
{bucketName, false, "", "JWT token missing"},
|
||||
}
|
||||
|
||||
|
||||
+19
-7
@@ -304,14 +304,26 @@ func newXLSets(endpoints EndpointList, format *formatXLV3, setCount int, drivesP
|
||||
// StorageInfo - combines output of StorageInfo across all erasure coded object sets.
|
||||
func (s *xlSets) StorageInfo(ctx context.Context) StorageInfo {
|
||||
var storageInfo StorageInfo
|
||||
var wg sync.WaitGroup
|
||||
|
||||
storageInfos := make([]StorageInfo, len(s.sets))
|
||||
storageInfo.Backend.Type = BackendErasure
|
||||
for _, set := range s.sets {
|
||||
lstorageInfo := set.StorageInfo(ctx)
|
||||
storageInfo.Used = storageInfo.Used + lstorageInfo.Used
|
||||
storageInfo.Total = storageInfo.Total + lstorageInfo.Total
|
||||
storageInfo.Available = storageInfo.Available + lstorageInfo.Available
|
||||
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks + lstorageInfo.Backend.OnlineDisks
|
||||
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks + lstorageInfo.Backend.OfflineDisks
|
||||
for index, set := range s.sets {
|
||||
wg.Add(1)
|
||||
go func(id int, set *xlObjects) {
|
||||
defer wg.Done()
|
||||
storageInfos[id] = set.StorageInfo(ctx)
|
||||
}(index, set)
|
||||
}
|
||||
// Wait for the go routines.
|
||||
wg.Wait()
|
||||
|
||||
for _, lstorageInfo := range storageInfos {
|
||||
storageInfo.Used += lstorageInfo.Used
|
||||
storageInfo.Total += lstorageInfo.Total
|
||||
storageInfo.Available += lstorageInfo.Available
|
||||
storageInfo.Backend.OnlineDisks += lstorageInfo.Backend.OnlineDisks
|
||||
storageInfo.Backend.OfflineDisks += lstorageInfo.Backend.OfflineDisks
|
||||
}
|
||||
|
||||
scData, scParity := getRedundancyCount(standardStorageClass, s.drivesPerSet)
|
||||
|
||||
+25
-10
@@ -19,6 +19,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bpool"
|
||||
@@ -70,24 +71,38 @@ func (d byDiskTotal) Less(i, j int) bool {
|
||||
// getDisksInfo - fetch disks info across all other storage API.
|
||||
func getDisksInfo(disks []StorageAPI) (disksInfo []DiskInfo, onlineDisks int, offlineDisks int) {
|
||||
disksInfo = make([]DiskInfo, len(disks))
|
||||
errs := make([]error, len(disks))
|
||||
var wg sync.WaitGroup
|
||||
for i, storageDisk := range disks {
|
||||
if storageDisk == nil {
|
||||
// Storage disk is empty, perhaps ignored disk or not available.
|
||||
offlineDisks++
|
||||
errs[i] = errDiskNotFound
|
||||
continue
|
||||
}
|
||||
info, err := storageDisk.DiskInfo()
|
||||
if err != nil {
|
||||
ctx := context.Background()
|
||||
logger.GetReqInfo(ctx).AppendTags("disk", storageDisk.String())
|
||||
logger.LogIf(ctx, err)
|
||||
if IsErr(err, baseErrs...) {
|
||||
offlineDisks++
|
||||
continue
|
||||
wg.Add(1)
|
||||
go func(id int, sDisk StorageAPI) {
|
||||
defer wg.Done()
|
||||
info, err := sDisk.DiskInfo()
|
||||
if err != nil {
|
||||
reqInfo := (&logger.ReqInfo{}).AppendTags("disk", sDisk.String())
|
||||
ctx := logger.SetReqInfo(context.Background(), reqInfo)
|
||||
logger.LogIf(ctx, err)
|
||||
if IsErr(err, baseErrs...) {
|
||||
errs[id] = err
|
||||
return
|
||||
}
|
||||
}
|
||||
disksInfo[id] = info
|
||||
}(i, storageDisk)
|
||||
}
|
||||
// Wait for the routines.
|
||||
wg.Wait()
|
||||
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
offlineDisks++
|
||||
}
|
||||
onlineDisks++
|
||||
disksInfo[i] = info
|
||||
}
|
||||
|
||||
// Success.
|
||||
|
||||
@@ -40,7 +40,12 @@ Disk caching caches objects for **downloaded** objects i.e
|
||||
- Bitrot protection is added to cached content and verified when object is served from cache.
|
||||
- When an object is deleted, corresponding entry in cache if any is deleted as well.
|
||||
- Cache continues to work for read-only operations such as GET, HEAD when backend is offline.
|
||||
- Cache-Control and Expires headers can be used to control how long objects stay in the cache
|
||||
- Cache-Control and Expires headers can be used to control how long objects stay in the cache. ETag of cached objects are not validated with backend until expiry time as per the Cache-Control or Expires header is met.
|
||||
- To ensure security guarantees, encrypted objects are normally not cached. However, if you wish to encrypt cached content on disk, you can set MINIO_CACHE_ENCRYPTION_MASTER_KEY environment variable to set a cache KMS
|
||||
master key to automatically encrypt all cached content.
|
||||
|
||||
Note that cache KMS master key is not recommended for use in production deployments. If the MinIO server/gateway machine is ever compromised, the cache KMS master key must also be treated as compromised.
|
||||
Support for external KMS to manage cache KMS keys is on the roadmap,and would be ideal for production use cases.
|
||||
|
||||
> NOTE: Expiration happens automatically based on the configured interval as explained above, frequently accessed objects stay alive in cache for a significantly longer time.
|
||||
|
||||
@@ -51,5 +56,3 @@ Upon restart of minio server after a running minio process is killed or crashes,
|
||||
- Bucket policies are not cached, so anonymous operations are not supported when backend is offline.
|
||||
- Objects are distributed using deterministic hashing among the list of configured cache drives. If one or more drives go offline, or cache drive configuration is altered in any way, performance may degrade to a linear lookup time depending on the number of disks in cache.
|
||||
|
||||
## TODO
|
||||
- Encrypt cached objects automatically with a cache encryption master key
|
||||
@@ -52,6 +52,7 @@ We found the following APIs to be redundant or less useful outside of AWS S3. If
|
||||
- ObjectACL (Use [bucket policies](https://docs.min.io/docs/minio-client-complete-guide#policy) instead)
|
||||
- ObjectTorrent
|
||||
- ObjectVersions
|
||||
- ObjectTagging
|
||||
|
||||
### Object name restrictions on MinIO
|
||||
Object names that contain characters `^*|\/&";` are unsupported on Windows and other file systems which do not support filenames with these characters. Note that this list is not exhaustive, and depends on the maintainers of the filesystem itself.
|
||||
|
||||
@@ -5,7 +5,7 @@ version: '3.7'
|
||||
# 9001 through 9004.
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
volumes:
|
||||
- data1:/data
|
||||
ports:
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
start_period: 3m
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
volumes:
|
||||
- data2:/data
|
||||
ports:
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
start_period: 3m
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
volumes:
|
||||
- data3:/data
|
||||
ports:
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
start_period: 3m
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
volumes:
|
||||
- data4:/data
|
||||
ports:
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -30,7 +30,7 @@ services:
|
||||
start_period: 3m
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -58,7 +58,7 @@ services:
|
||||
start_period: 3m
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -86,7 +86,7 @@ services:
|
||||
start_period: 3m
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -34,7 +34,7 @@ services:
|
||||
start_period: 3m
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
start_period: 3m
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -98,7 +98,7 @@ services:
|
||||
start_period: 3m
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -30,7 +30,7 @@ spec:
|
||||
value: "minio"
|
||||
- name: MINIO_SECRET_KEY
|
||||
value: "minio123"
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
# Unfortunately you must manually define each server. Perhaps autodiscovery via DNS can be implemented in the future.
|
||||
args:
|
||||
- server
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
value: "minio"
|
||||
- name: MINIO_SECRET_KEY
|
||||
value: "minio123"
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
args:
|
||||
- server
|
||||
- http://minio-0.minio.default.svc.cluster.local/data
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
containers:
|
||||
- name: minio
|
||||
# Pulls the default Minio image from Docker Hub
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
args:
|
||||
- gateway
|
||||
- gcs
|
||||
|
||||
@@ -29,7 +29,7 @@ spec:
|
||||
- name: data
|
||||
mountPath: "/data"
|
||||
# Pulls the lastest Minio image from Docker Hub
|
||||
image: minio/minio:RELEASE.2019-08-14T20-37-41Z
|
||||
image: minio/minio:RELEASE.2019-08-21T19-40-07Z
|
||||
args:
|
||||
- server
|
||||
- /data
|
||||
|
||||
@@ -92,7 +92,7 @@ require (
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
|
||||
go.etcd.io/bbolt v1.3.3 // indirect
|
||||
go.uber.org/atomic v1.3.2
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
|
||||
google.golang.org/api v0.4.0
|
||||
|
||||
@@ -651,8 +651,8 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
||||
+36
-38
@@ -173,7 +173,7 @@ const directioAlignSize = 4096
|
||||
// used with DIRECT I/O based file descriptor and it is expected that
|
||||
// input writer *os.File not a generic io.Writer. Make sure to have
|
||||
// the file opened for writes with syscall.O_DIRECT flag.
|
||||
func CopyAligned(w *os.File, r io.Reader, alignedBuf []byte) (int64, error) {
|
||||
func CopyAligned(w *os.File, r io.Reader, alignedBuf []byte, totalSize int64) (int64, error) {
|
||||
// Writes remaining bytes in the buffer.
|
||||
writeUnaligned := func(w *os.File, buf []byte) (remainingWritten int, err error) {
|
||||
var n int
|
||||
@@ -205,47 +205,45 @@ func CopyAligned(w *os.File, r io.Reader, alignedBuf []byte) (int64, error) {
|
||||
}
|
||||
|
||||
var written int64
|
||||
var err error
|
||||
for {
|
||||
nr, er := r.Read(alignedBuf)
|
||||
if nr == len(alignedBuf) {
|
||||
// Buffer read is aligned with input buffer, proceed to write.
|
||||
nw, ew := w.Write(alignedBuf)
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
} else if nr > 0 {
|
||||
// Buffer read is not aligned with input buffer, proceed to write
|
||||
// whatever possible as aligned and turn off direct I/O.
|
||||
nw, ew := writeUnaligned(w, alignedBuf[:nr])
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
buf := alignedBuf
|
||||
if totalSize != -1 {
|
||||
remaining := totalSize - written
|
||||
if remaining < int64(len(buf)) {
|
||||
buf = buf[:remaining]
|
||||
}
|
||||
}
|
||||
// For any read errors break out and return error.
|
||||
if er != nil {
|
||||
if er != io.EOF {
|
||||
err = er
|
||||
nr, err := io.ReadFull(r, buf)
|
||||
eof := err == io.EOF || err == io.ErrUnexpectedEOF
|
||||
if err != nil && !eof {
|
||||
return written, err
|
||||
}
|
||||
buf = buf[:nr]
|
||||
var nw int
|
||||
if len(buf)%directioAlignSize == 0 {
|
||||
// buf is aligned for directio write()
|
||||
nw, err = w.Write(buf)
|
||||
} else {
|
||||
// buf is not aligned, hence use writeUnaligned()
|
||||
nw, err = writeUnaligned(w, buf)
|
||||
}
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
if nw != len(buf) {
|
||||
return written, io.ErrShortWrite
|
||||
}
|
||||
|
||||
if totalSize != -1 {
|
||||
if written == totalSize {
|
||||
return written, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
if eof {
|
||||
return written, nil
|
||||
}
|
||||
}
|
||||
|
||||
return written, err
|
||||
}
|
||||
|
||||
+82
-70
@@ -31,24 +31,25 @@ func main() {
|
||||
}
|
||||
|
||||
// Fetch service status.
|
||||
st, err := mdmClnt.ServiceStatus()
|
||||
st, err := mdmClnt.ServerInfo()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%#v\n", st)
|
||||
for _, peerInfo := range serversInfo {
|
||||
log.Printf("Node: %s, Info: %v\n", peerInfo.Addr, peerInfo.Data)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
| Service operations | Info operations | Healing operations | Config operations | Top operations | IAM operations | Misc |
|
||||
|:------------------------------------------|:--------------------------------------------|:-------------------|:----------------------------------|:------------------------|:--------------------------------------|:--------------------------------------------------|
|
||||
| [`ServiceStatus`](#ServiceStatus) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`TopLocks`](#TopLocks) | [`AddUser`](#AddUser) | |
|
||||
| [`ServiceSendAction`](#ServiceSendAction) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) |
|
||||
| [`Trace`](#Trace) | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | [`GetConfigKeys`](#GetConfigKeys) | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) |
|
||||
| | | | [`SetConfigKeys`](#SetConfigKeys) | | [`AddCannedPolicy`](#AddCannedPolicy) | |
|
||||
|
||||
|
||||
| Service operations | Info operations | Healing operations | Config operations | Top operations | IAM operations | Misc |
|
||||
|:------------------------------------|:------------------------------------------------|:-------------------|:----------------------------------|:------------------------|:--------------------------------------|:--------------------------------------------------|
|
||||
| [`ServiceRestart`](#ServiceRestart) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`TopLocks`](#TopLocks) | [`AddUser`](#AddUser) | |
|
||||
| [`ServiceStop`](#ServiceStop) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) |
|
||||
| | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | [`GetConfigKeys`](#GetConfigKeys) | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) |
|
||||
| [`ServiceTrace`](#ServiceTrace) | [`ServerDrivesPerfInfo`](#ServerDrivesPerfInfo) | | [`SetConfigKeys`](#SetConfigKeys) | | [`AddCannedPolicy`](#AddCannedPolicy) | [`ServerUpdate`](#ServerUpdate) |
|
||||
|
||||
## 1. Constructor
|
||||
<a name="MinIO"></a>
|
||||
|
||||
@@ -64,25 +65,7 @@ __Parameters__
|
||||
| `secretAccessKey` | _string_ | Secret key for the object storage endpoint. |
|
||||
| `ssl` | _bool_ | Set this value to 'true' to enable secure (HTTPS) access. |
|
||||
|
||||
## 2. Admin API Version
|
||||
|
||||
<a name="VersionInfo"></a>
|
||||
### VersionInfo() (AdminAPIVersionInfo, error)
|
||||
Fetch server's supported Administrative API version.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
|
||||
info, err := madmClnt.VersionInfo()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("%s\n", info.Version)
|
||||
|
||||
```
|
||||
|
||||
## 3. Service operations
|
||||
## 2. Service operations
|
||||
|
||||
<a name="ServiceStatus"></a>
|
||||
### ServiceStatus() (ServiceStatusMetadata, error)
|
||||
@@ -111,25 +94,55 @@ Fetch service status, replies disk space used, backend type and total disks offl
|
||||
|
||||
```
|
||||
|
||||
<a name="ServiceSendAction"></a>
|
||||
### ServiceSendAction(act ServiceActionValue) (error)
|
||||
Sends a service action command to service - possible actions are restarting and stopping the server.
|
||||
<a name="ServiceRestart"></a>
|
||||
### ServiceRestart() error
|
||||
Sends a service action restart command to MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
```go
|
||||
// To restart the service, restarts all servers in the cluster.
|
||||
err := madmClnt.ServiceRestart()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
```
|
||||
|
||||
```go
|
||||
// to restart
|
||||
st, err := madmClnt.ServiceSendAction(ServiceActionValueRestart)
|
||||
// or to stop
|
||||
// st, err := madmClnt.ServiceSendAction(ServiceActionValueStop)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("Success")
|
||||
```
|
||||
<a name="ServiceStop"></a>
|
||||
### ServiceStop() error
|
||||
Sends a service action stop command to MinIO server.
|
||||
|
||||
## 4. Info operations
|
||||
__Example__
|
||||
|
||||
```go
|
||||
// To stop the service, stops all servers in the cluster.
|
||||
err := madmClnt.ServiceStop()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
```
|
||||
|
||||
<a name="ServiceTrace"></a>
|
||||
### ServiceTrace(allTrace bool, doneCh <-chan struct{}) <-chan TraceInfo
|
||||
Enable HTTP request tracing on all nodes in a MinIO cluster
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
// listen to all trace including internal API calls
|
||||
allTrace := true
|
||||
// Start listening on all trace activity.
|
||||
traceCh := madmClnt.ServiceTrace(allTrace, doneCh)
|
||||
for traceInfo := range traceCh {
|
||||
fmt.Println(traceInfo.String())
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Info operations
|
||||
|
||||
<a name="ServerInfo"></a>
|
||||
### ServerInfo() ([]ServerInfo, error)
|
||||
@@ -213,7 +226,7 @@ Fetches information for all cluster nodes, such as server properties, storage in
|
||||
<a name="ServerDrivesPerfInfo"></a>
|
||||
### ServerDrivesPerfInfo() ([]ServerDrivesPerfInfo, error)
|
||||
|
||||
Fetches drive performance information for all cluster nodes. Returned value is in Bytes/s.
|
||||
Fetches drive performance information for all cluster nodes.
|
||||
|
||||
| Param | Type | Description |
|
||||
|-----------------|--------------------|--------------------------------------------------------------------|
|
||||
@@ -231,7 +244,7 @@ Fetches drive performance information for all cluster nodes. Returned value is i
|
||||
<a name="ServerCPULoadInfo"></a>
|
||||
### ServerCPULoadInfo() ([]ServerCPULoadInfo, error)
|
||||
|
||||
Fetches CPU utilization for all cluster nodes. Returned value is in Bytes.
|
||||
Fetches CPU utilization for all cluster nodes.
|
||||
|
||||
| Param | Type | Description |
|
||||
|----------------|------------|---------------------------------------------------------------------|
|
||||
@@ -249,7 +262,7 @@ Fetches CPU utilization for all cluster nodes. Returned value is in Bytes.
|
||||
<a name="ServerMemUsageInfo"></a>
|
||||
### ServerMemUsageInfo() ([]ServerMemUsageInfo, error)
|
||||
|
||||
Fetches Mem utilization for all cluster nodes. Returned value is in Bytes.
|
||||
Fetches Mem utilization for all cluster nodes.
|
||||
|
||||
| Param | Type | Description |
|
||||
|-----------------|-------------|---------------------------------------------------------------------|
|
||||
@@ -262,7 +275,7 @@ Fetches Mem utilization for all cluster nodes. Returned value is in Bytes.
|
||||
| `mem.Usage.Mem` | _uint64_ | The total number of bytes obtained from the OS |
|
||||
| `mem.Usage.Error` | _string_ | Error (if any) encountered while accesing the CPU info |
|
||||
|
||||
## 6. Heal operations
|
||||
## 5. Heal operations
|
||||
|
||||
<a name="Heal"></a>
|
||||
### Heal(bucket, prefix string, healOpts HealOpts, clientToken string, forceStart bool, forceStop bool) (start HealStartSuccess, status HealTaskStatus, err error)
|
||||
@@ -327,7 +340,7 @@ __Example__
|
||||
| `DiskInfo.AvailableOn` | _[]int_ | List of disks on which the healed entity is present and healthy |
|
||||
| `DiskInfo.HealedOn` | _[]int_ | List of disks on which the healed entity was restored |
|
||||
|
||||
## 7. Config operations
|
||||
## 6. Config operations
|
||||
|
||||
<a name="GetConfig"></a>
|
||||
### GetConfig() ([]byte, error)
|
||||
@@ -405,7 +418,7 @@ __Example__
|
||||
log.Println("New configuration successfully set")
|
||||
```
|
||||
|
||||
## 8. Top operations
|
||||
## 7. Top operations
|
||||
|
||||
<a name="TopLocks"></a>
|
||||
### TopLocks() (LockEntries, error)
|
||||
@@ -427,7 +440,7 @@ __Example__
|
||||
log.Println("TopLocks received successfully: ", string(out))
|
||||
```
|
||||
|
||||
## 9. IAM operations
|
||||
## 8. IAM operations
|
||||
|
||||
<a name="AddCannedPolicy"></a>
|
||||
### AddCannedPolicy(policyName string, policy string) error
|
||||
@@ -483,7 +496,25 @@ __Example__
|
||||
}
|
||||
```
|
||||
|
||||
## 10. Misc operations
|
||||
## 9. Misc operations
|
||||
|
||||
<a name="ServerUpdate"></a>
|
||||
### ServerUpdate(updateURL string) (ServerUpdateStatus, error)
|
||||
Sends a update command to MinIO server, to update MinIO server to latest release. In distributed setup it updates all servers atomically.
|
||||
|
||||
__Example__
|
||||
|
||||
```go
|
||||
// Updates all servers and restarts all the servers in the cluster.
|
||||
// optionally takes an updateURL, which is used to update the binary.
|
||||
us, err := madmClnt.ServerUpdate(updateURL)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if us.CurrentVersion != us.UpdatedVersion {
|
||||
log.Printf("Updated server version from %s to %s successfully", us.CurrentVersion, us.UpdatedVersion)
|
||||
}
|
||||
```
|
||||
|
||||
<a name="StartProfiling"></a>
|
||||
### StartProfiling(profiler string) error
|
||||
@@ -537,22 +568,3 @@ __Example__
|
||||
|
||||
log.Println("Profiling data successfully downloaded.")
|
||||
```
|
||||
|
||||
<a name="Trace"></a>
|
||||
### Trace(allTrace bool,doneCh <-chan struct{}) <-chan TraceInfo
|
||||
Enable HTTP request tracing on all nodes in a MinIO cluster
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
// listen to all trace including internal API calls
|
||||
allTrace := true
|
||||
// Start listening on all trace activity.
|
||||
traceCh := madmClnt.Trace(allTrace,doneCh)
|
||||
for traceInfo := range traceCh {
|
||||
fmt.Println(traceInfo.String())
|
||||
}
|
||||
log.Println("Success")
|
||||
```
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2019 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 madmin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
trace "github.com/minio/minio/pkg/trace"
|
||||
)
|
||||
|
||||
// TraceInfo holds http trace
|
||||
type TraceInfo struct {
|
||||
Trace trace.Info
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
// Trace - listen on http trace notifications.
|
||||
func (adm AdminClient) Trace(allTrace, errTrace bool, doneCh <-chan struct{}) <-chan TraceInfo {
|
||||
traceInfoCh := make(chan TraceInfo)
|
||||
// Only success, start a routine to start reading line by line.
|
||||
go func(traceInfoCh chan<- TraceInfo) {
|
||||
defer close(traceInfoCh)
|
||||
for {
|
||||
urlValues := make(url.Values)
|
||||
urlValues.Set("all", strconv.FormatBool(allTrace))
|
||||
urlValues.Set("err", strconv.FormatBool(errTrace))
|
||||
reqData := requestData{
|
||||
relPath: "/v1/trace",
|
||||
queryValues: urlValues,
|
||||
}
|
||||
// Execute GET to call trace handler
|
||||
resp, err := adm.executeMethod("GET", reqData)
|
||||
if err != nil {
|
||||
closeResponse(resp)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
traceInfoCh <- TraceInfo{Err: httpRespToErrorResponse(resp)}
|
||||
return
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
for {
|
||||
var info trace.Info
|
||||
if err = dec.Decode(&info); err != nil {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-doneCh:
|
||||
return
|
||||
case traceInfoCh <- TraceInfo{Trace: info}:
|
||||
}
|
||||
}
|
||||
}
|
||||
}(traceInfoCh)
|
||||
|
||||
// Returns the trace info channel, for caller to start reading from.
|
||||
return traceInfoCh
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -31,7 +31,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -31,7 +31,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -31,7 +31,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -32,7 +32,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -32,7 +32,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -35,7 +35,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2016 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 main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
st, err := madmClnt.ServiceStatus()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(st)
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
@@ -43,7 +43,7 @@ func main() {
|
||||
// in the minio cluster.
|
||||
allTrace := false
|
||||
errTrace := false
|
||||
traceCh := madmClnt.Trace(allTrace, errTrace, doneCh)
|
||||
traceCh := madmClnt.ServiceTrace(allTrace, errTrace, doneCh)
|
||||
for traceInfo := range traceCh {
|
||||
if traceInfo.Err != nil {
|
||||
fmt.Println(traceInfo.Err)
|
||||
@@ -33,7 +33,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -130,7 +130,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,7 @@ func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2016, 2017 MinIO, Inc.
|
||||
* MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,81 +21,103 @@ import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
trace "github.com/minio/minio/pkg/trace"
|
||||
)
|
||||
|
||||
// ServerVersion - server version
|
||||
type ServerVersion struct {
|
||||
Version string `json:"version"`
|
||||
CommitID string `json:"commitID"`
|
||||
// ServiceRestart - restarts the MinIO cluster
|
||||
func (adm *AdminClient) ServiceRestart() error {
|
||||
_, err := adm.serviceCallAction(ServiceActionRestart)
|
||||
return err
|
||||
}
|
||||
|
||||
// ServiceStatus - contains the response of service status API
|
||||
type ServiceStatus struct {
|
||||
ServerVersion ServerVersion `json:"serverVersion"`
|
||||
Uptime time.Duration `json:"uptime"`
|
||||
// ServiceStop - stops the MinIO cluster
|
||||
func (adm *AdminClient) ServiceStop() error {
|
||||
_, err := adm.serviceCallAction(ServiceActionStop)
|
||||
return err
|
||||
}
|
||||
|
||||
// ServiceStatus - Connect to a minio server and call Service Status
|
||||
// Management API to fetch server's storage information represented by
|
||||
// ServiceStatusMetadata structure
|
||||
func (adm *AdminClient) ServiceStatus() (ss ServiceStatus, err error) {
|
||||
// Request API to GET service status
|
||||
resp, err := adm.executeMethod("GET", requestData{relPath: "/v1/service"})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// Check response http status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return ss, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(respBytes, &ss)
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// ServiceActionValue - type to restrict service-action values
|
||||
type ServiceActionValue string
|
||||
// ServiceAction - type to restrict service-action values
|
||||
type ServiceAction string
|
||||
|
||||
const (
|
||||
// ServiceActionValueRestart represents restart action
|
||||
ServiceActionValueRestart ServiceActionValue = "restart"
|
||||
// ServiceActionValueStop represents stop action
|
||||
ServiceActionValueStop = "stop"
|
||||
// ServiceActionRestart represents restart action
|
||||
ServiceActionRestart ServiceAction = "restart"
|
||||
// ServiceActionStop represents stop action
|
||||
ServiceActionStop = "stop"
|
||||
)
|
||||
|
||||
// ServiceAction - represents POST body for service action APIs
|
||||
type ServiceAction struct {
|
||||
Action ServiceActionValue `json:"action"`
|
||||
}
|
||||
|
||||
// ServiceSendAction - Call Service Restart/Stop API to restart/stop a
|
||||
// MinIO server
|
||||
func (adm *AdminClient) ServiceSendAction(action ServiceActionValue) error {
|
||||
body, err := json.Marshal(ServiceAction{action})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// serviceCallAction - call service restart/update/stop API.
|
||||
func (adm *AdminClient) serviceCallAction(action ServiceAction) ([]byte, error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("action", string(action))
|
||||
|
||||
// Request API to Restart server
|
||||
resp, err := adm.executeMethod("POST", requestData{
|
||||
relPath: "/v1/service",
|
||||
content: body,
|
||||
relPath: "/v1/service",
|
||||
queryValues: queryValues,
|
||||
})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
return nil
|
||||
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
// ServiceTraceInfo holds http trace
|
||||
type ServiceTraceInfo struct {
|
||||
Trace trace.Info
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
// ServiceTrace - listen on http trace notifications.
|
||||
func (adm AdminClient) ServiceTrace(allTrace, errTrace bool, doneCh <-chan struct{}) <-chan ServiceTraceInfo {
|
||||
traceInfoCh := make(chan ServiceTraceInfo)
|
||||
// Only success, start a routine to start reading line by line.
|
||||
go func(traceInfoCh chan<- ServiceTraceInfo) {
|
||||
defer close(traceInfoCh)
|
||||
for {
|
||||
urlValues := make(url.Values)
|
||||
urlValues.Set("all", strconv.FormatBool(allTrace))
|
||||
urlValues.Set("err", strconv.FormatBool(errTrace))
|
||||
reqData := requestData{
|
||||
relPath: "/v1/trace",
|
||||
queryValues: urlValues,
|
||||
}
|
||||
// Execute GET to call trace handler
|
||||
resp, err := adm.executeMethod("GET", reqData)
|
||||
if err != nil {
|
||||
closeResponse(resp)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
traceInfoCh <- ServiceTraceInfo{Err: httpRespToErrorResponse(resp)}
|
||||
return
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
for {
|
||||
var info trace.Info
|
||||
if err = dec.Decode(&info); err != nil {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-doneCh:
|
||||
return
|
||||
case traceInfoCh <- ServiceTraceInfo{Trace: info}:
|
||||
}
|
||||
}
|
||||
}
|
||||
}(traceInfoCh)
|
||||
|
||||
// Returns the trace info channel, for caller to start reading from.
|
||||
return traceInfoCh
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2019 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 madmin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ServerUpdateStatus - contains the response of service update API
|
||||
type ServerUpdateStatus struct {
|
||||
CurrentVersion string `json:"currentVersion"`
|
||||
UpdatedVersion string `json:"updatedVersion"`
|
||||
}
|
||||
|
||||
// ServerUpdate - updates and restarts the MinIO cluster to latest version.
|
||||
// optionally takes an input URL to specify a custom update binary link
|
||||
func (adm *AdminClient) ServerUpdate(updateURL string) (us ServerUpdateStatus, err error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("updateURL", updateURL)
|
||||
|
||||
// Request API to Restart server
|
||||
resp, err := adm.executeMethod("POST", requestData{
|
||||
relPath: "/v1/update",
|
||||
queryValues: queryValues,
|
||||
})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return us, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return us, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return us, err
|
||||
}
|
||||
err = json.Unmarshal(buf, &us)
|
||||
return us, err
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 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 madmin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// AdminAPIVersionInfo - contains admin API version information
|
||||
type AdminAPIVersionInfo struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// VersionInfo - Connect to minio server and call the version API to
|
||||
// retrieve the server API version
|
||||
func (adm *AdminClient) VersionInfo() (verInfo AdminAPIVersionInfo, err error) {
|
||||
var resp *http.Response
|
||||
resp, err = adm.executeMethod("GET", requestData{relPath: "/version"})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return verInfo, err
|
||||
}
|
||||
|
||||
// Check response http status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return verInfo, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return verInfo, err
|
||||
}
|
||||
|
||||
// Unmarshal the server's json response
|
||||
err = json.Unmarshal(respBytes, &verInfo)
|
||||
return verInfo, err
|
||||
}
|
||||
Reference in New Issue
Block a user