mirror of
https://github.com/minio/minio.git
synced 2025-01-11 15:03:22 -05:00
Implement cluster-wide in-place updates (#8070)
This PR is a breaking change and also deprecates `minio update` command, from this release onwards all users are advised to just use `mc admin update`
This commit is contained in:
parent
70136fb55b
commit
d65a2c6725
@ -21,7 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -66,106 +66,121 @@ const (
|
|||||||
mgmtForceStop = "forceStop"
|
mgmtForceStop = "forceStop"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func updateServer() (us madmin.ServiceUpdateStatus, err error) {
|
||||||
// This struct literal represents the Admin API version that
|
minioMode := getMinioMode()
|
||||||
// the server uses.
|
updateMsg, sha256Hex, _, latestReleaseTime, err := getUpdateInfo(updateTimeout, minioMode)
|
||||||
adminAPIVersionInfo = madmin.AdminAPIVersionInfo{
|
|
||||||
Version: "1",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// VersionHandler - GET /minio/admin/version
|
|
||||||
// -----------
|
|
||||||
// Returns Administration API version
|
|
||||||
func (a adminAPIHandlers) VersionHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "Version")
|
|
||||||
|
|
||||||
objectAPI := validateAdminReq(ctx, w, r)
|
|
||||||
if objectAPI == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonBytes, err := json.Marshal(adminAPIVersionInfo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
return us, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if updateMsg == "" {
|
||||||
writeSuccessResponseJSON(w, jsonBytes)
|
us.CurrentVersion = Version
|
||||||
|
us.UpdatedVersion = Version
|
||||||
|
return us, nil
|
||||||
|
}
|
||||||
|
if err = doUpdate(sha256Hex, minioMode, latestReleaseTime, true); err != nil {
|
||||||
|
return us, err
|
||||||
|
}
|
||||||
|
us.CurrentVersion = Version
|
||||||
|
us.UpdatedVersion = latestReleaseTime.Format(minioReleaseTagTimeLayout)
|
||||||
|
return us, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceStatusHandler - GET /minio/admin/v1/service
|
// ServiceActionHandler - POST /minio/admin/v1/service
|
||||||
|
// Body: {"action": <action>}
|
||||||
// ----------
|
// ----------
|
||||||
// Returns server version and uptime.
|
// restarts/updates/stops minio server gracefully. In a distributed setup,
|
||||||
func (a adminAPIHandlers) ServiceStatusHandler(w http.ResponseWriter, r *http.Request) {
|
// restarts/updates/stops all the servers in the cluster. Also asks for
|
||||||
ctx := newContext(r, w, "ServiceStatus")
|
// server version and uptime.
|
||||||
|
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)
|
objectAPI := validateAdminReq(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
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)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceStopNRestartHandler - POST /minio/admin/v1/service
|
|
||||||
// Body: {"action": <restart-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")
|
|
||||||
|
|
||||||
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
|
var serviceSig serviceSignal
|
||||||
switch sa.Action {
|
switch madmin.ServiceAction(action) {
|
||||||
case madmin.ServiceActionValueRestart:
|
case madmin.ServiceActionRestart:
|
||||||
serviceSig = serviceRestart
|
serviceSig = serviceRestart
|
||||||
case madmin.ServiceActionValueStop:
|
case madmin.ServiceActionStop:
|
||||||
serviceSig = serviceStop
|
serviceSig = serviceStop
|
||||||
|
case madmin.ServiceActionUpdate:
|
||||||
|
if globalInplaceUpdateDisabled {
|
||||||
|
// if MINIO_UPDATE=off - inplace update is disabled, mostly
|
||||||
|
// in containers.
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// update, updates the server and restarts them.
|
||||||
|
serviceSig = serviceUpdate | serviceRestart
|
||||||
|
case madmin.ServiceActionStatus:
|
||||||
|
serviceSig = serviceStatus
|
||||||
default:
|
default:
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action))
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
||||||
logger.LogIf(ctx, errors.New("Invalid service action received"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reply to the client before restarting minio server.
|
if serviceSig&serviceUpdate == serviceUpdate {
|
||||||
writeSuccessResponseHeadersOnly(w)
|
for _, nerr := range globalNotificationSys.SignalService(serviceSig) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus, err := updateServer()
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal API response
|
||||||
|
jsonBytes, err := json.Marshal(updateStatus)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, jsonBytes)
|
||||||
|
|
||||||
|
if updateStatus.CurrentVersion != updateStatus.UpdatedVersion {
|
||||||
|
// We did upgrade - restart all services.
|
||||||
|
globalServiceSignalCh <- serviceSig
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceSig == serviceStatus {
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, jsonBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Notify all other MinIO peers signal service.
|
// Notify all other MinIO peers signal service.
|
||||||
for _, nerr := range globalNotificationSys.SignalService(serviceSig) {
|
for _, nerr := range globalNotificationSys.SignalService(serviceSig) {
|
||||||
@ -175,6 +190,9 @@ func (a adminAPIHandlers) ServiceStopNRestartHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reply to the client before restarting, stopping MinIO server.
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
|
||||||
globalServiceSignalCh <- serviceSig
|
globalServiceSignalCh <- serviceSig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -989,6 +1007,24 @@ func (a adminAPIHandlers) GetConfigKeysHandler(w http.ResponseWriter, r *http.Re
|
|||||||
writeSuccessResponseJSON(w, []byte(econfigData))
|
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
|
// toAdminAPIErrCode - converts errXLWriteQuorum error to admin API
|
||||||
// specific error.
|
// specific error.
|
||||||
func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
||||||
@ -1001,7 +1037,21 @@ func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toAdminAPIErr(ctx context.Context, err error) APIError {
|
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>
|
// RemoveUser - DELETE /minio/admin/v1/remove-user?accessKey=<access_key>
|
||||||
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -344,41 +344,6 @@ func initTestXLObjLayer() (ObjectLayer, []string, error) {
|
|||||||
return objLayer, xlDirs, nil
|
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
|
// cmdType - Represents different service subcomands like status, stop
|
||||||
// and restart.
|
// and restart.
|
||||||
type cmdType int
|
type cmdType int
|
||||||
@ -387,52 +352,8 @@ const (
|
|||||||
statusCmd cmdType = iota
|
statusCmd cmdType = iota
|
||||||
restartCmd
|
restartCmd
|
||||||
stopCmd
|
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
|
// toServiceSignal - Helper function that translates a given cmdType
|
||||||
// value to its corresponding serviceSignal value.
|
// value to its corresponding serviceSignal value.
|
||||||
func (c cmdType) toServiceSignal() serviceSignal {
|
func (c cmdType) toServiceSignal() serviceSignal {
|
||||||
@ -447,14 +368,16 @@ func (c cmdType) toServiceSignal() serviceSignal {
|
|||||||
return serviceStatus
|
return serviceStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cmdType) toServiceActionValue() madmin.ServiceActionValue {
|
func (c cmdType) toServiceAction() madmin.ServiceAction {
|
||||||
switch c {
|
switch c {
|
||||||
case restartCmd:
|
case restartCmd:
|
||||||
return madmin.ServiceActionValueRestart
|
return madmin.ServiceActionRestart
|
||||||
case stopCmd:
|
case stopCmd:
|
||||||
return madmin.ServiceActionValueStop
|
return madmin.ServiceActionStop
|
||||||
|
case statusCmd:
|
||||||
|
return madmin.ServiceActionStatus
|
||||||
}
|
}
|
||||||
return madmin.ServiceActionValueStop
|
return madmin.ServiceActionStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// testServiceSignalReceiver - Helper function that simulates a
|
// testServiceSignalReceiver - Helper function that simulates a
|
||||||
@ -469,19 +392,15 @@ func testServiceSignalReceiver(cmd cmdType, t *testing.T) {
|
|||||||
|
|
||||||
// getServiceCmdRequest - Constructs a management REST API request for service
|
// getServiceCmdRequest - Constructs a management REST API request for service
|
||||||
// subcommands for a given cmdType value.
|
// subcommands for a given cmdType value.
|
||||||
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials, body []byte) (*http.Request, error) {
|
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials) (*http.Request, error) {
|
||||||
req, err := newTestRequest(cmd.apiMethod(), cmd.apiEndpoint(), 0, nil)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// management REST API uses signature V4 for authentication.
|
||||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -517,13 +436,7 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
|||||||
}
|
}
|
||||||
credentials := globalServerConfig.GetCredential()
|
credentials := globalServerConfig.GetCredential()
|
||||||
|
|
||||||
body, err := json.Marshal(madmin.ServiceAction{
|
req, err := getServiceCmdRequest(cmd, credentials)
|
||||||
Action: cmd.toServiceActionValue()})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("JSONify error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := getServiceCmdRequest(cmd, credentials, body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to build service status request %v", err)
|
t.Fatalf("Failed to build service status request %v", err)
|
||||||
}
|
}
|
||||||
@ -531,6 +444,11 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
|||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
adminTestBed.router.ServeHTTP(rec, req)
|
adminTestBed.router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
resp, _ := ioutil.ReadAll(rec.Body)
|
||||||
|
t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
|
||||||
|
http.StatusOK, rec.Code, string(resp))
|
||||||
|
}
|
||||||
if cmd == statusCmd {
|
if cmd == statusCmd {
|
||||||
expectedInfo := madmin.ServiceStatus{
|
expectedInfo := madmin.ServiceStatus{
|
||||||
ServerVersion: madmin.ServerVersion{Version: Version, CommitID: CommitID},
|
ServerVersion: madmin.ServerVersion{Version: Version, CommitID: CommitID},
|
||||||
@ -544,12 +462,6 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
resp, _ := ioutil.ReadAll(rec.Body)
|
|
||||||
t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
|
|
||||||
http.StatusOK, rec.Code, string(resp))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until testServiceSignalReceiver() called in a goroutine quits.
|
// Wait until testServiceSignalReceiver() called in a goroutine quits.
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
@ -38,17 +38,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
adminRouter := router.PathPrefix(adminAPIPathPrefix).Subrouter()
|
adminRouter := router.PathPrefix(adminAPIPathPrefix).Subrouter()
|
||||||
|
|
||||||
// Version handler
|
// Version handler
|
||||||
adminRouter.Methods(http.MethodGet).Path("/version").HandlerFunc(httpTraceAll(adminAPI.VersionHandler))
|
|
||||||
|
|
||||||
adminV1Router := adminRouter.PathPrefix("/v1").Subrouter()
|
adminV1Router := adminRouter.PathPrefix("/v1").Subrouter()
|
||||||
|
|
||||||
/// Service operations
|
/// Service operations
|
||||||
|
|
||||||
// Service status
|
// Get status, update, restart and stop MinIO service.
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceStatusHandler))
|
adminV1Router.Methods(http.MethodPost).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceActionHandler)).Queries("action", "{action:.*}")
|
||||||
|
|
||||||
// Service restart and stop - TODO
|
|
||||||
adminV1Router.Methods(http.MethodPost).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceStopNRestartHandler))
|
|
||||||
|
|
||||||
// Info operations
|
// Info operations
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler))
|
adminV1Router.Methods(http.MethodGet).Path("/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler))
|
||||||
|
@ -339,18 +339,6 @@ var (
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf
|
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.
|
// Returns minio global information, as a key value map.
|
||||||
|
@ -116,7 +116,6 @@ func newApp(name string) *cli.App {
|
|||||||
// Register all commands.
|
// Register all commands.
|
||||||
registerCommand(serverCmd)
|
registerCommand(serverCmd)
|
||||||
registerCommand(gatewayCmd)
|
registerCommand(gatewayCmd)
|
||||||
registerCommand(updateCmd)
|
|
||||||
registerCommand(versionCmd)
|
registerCommand(versionCmd)
|
||||||
|
|
||||||
// Set up app.
|
// Set up app.
|
||||||
|
@ -506,7 +506,7 @@ func (client *peerRESTClient) LoadGroup(group string) error {
|
|||||||
// SignalService - sends signal to peer nodes.
|
// SignalService - sends signal to peer nodes.
|
||||||
func (client *peerRESTClient) SignalService(sig serviceSignal) error {
|
func (client *peerRESTClient) SignalService(sig serviceSignal) error {
|
||||||
values := make(url.Values)
|
values := make(url.Values)
|
||||||
values.Set(peerRESTSignal, string(sig))
|
values.Set(peerRESTSignal, strconv.Itoa(int(sig)))
|
||||||
respBody, err := client.call(peerRESTMethodSignalService, values, nil, -1)
|
respBody, err := client.call(peerRESTMethodSignalService, values, nil, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -824,10 +824,29 @@ func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Req
|
|||||||
s.writeErrorResponse(w, errors.New("signal name is missing"))
|
s.writeErrorResponse(w, errors.New("signal name is missing"))
|
||||||
return
|
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()
|
defer w.(http.Flusher).Flush()
|
||||||
switch signal {
|
switch {
|
||||||
case serviceRestart, serviceStop:
|
case signal&serviceUpdate == serviceUpdate:
|
||||||
|
us, err := updateServer()
|
||||||
|
if err != nil {
|
||||||
|
s.writeErrorResponse(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// We didn't upgrade, no need to restart
|
||||||
|
// the services.
|
||||||
|
if us.CurrentVersion == us.UpdatedVersion {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case signal&serviceRestart == serviceRestart:
|
||||||
|
fallthrough
|
||||||
|
case signal&serviceStop == serviceStop:
|
||||||
globalServiceSignalCh <- signal
|
globalServiceSignalCh <- signal
|
||||||
default:
|
default:
|
||||||
s.writeErrorResponse(w, errUnsupportedSignal)
|
s.writeErrorResponse(w, errUnsupportedSignal)
|
||||||
|
@ -23,12 +23,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Type of service signals currently supported.
|
// Type of service signals currently supported.
|
||||||
type serviceSignal string
|
type serviceSignal int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
serviceStatus serviceSignal = "serviceStatus" // Gets status about the service.
|
serviceStatus serviceSignal = iota // Gets status about the service.
|
||||||
serviceRestart = "serviceRestart" // Restarts the service.
|
serviceRestart // Restarts the service.
|
||||||
serviceStop = "serviceStop" // Stops the server.
|
serviceStop // Stops the server.
|
||||||
|
serviceUpdate // Updates the server.
|
||||||
// Add new service requests here.
|
// Add new service requests here.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -77,16 +77,14 @@ func handleSignals() {
|
|||||||
logger.Info("Exiting on signal: %s", strings.ToUpper(osSignal.String()))
|
logger.Info("Exiting on signal: %s", strings.ToUpper(osSignal.String()))
|
||||||
exit(stopProcess())
|
exit(stopProcess())
|
||||||
case signal := <-globalServiceSignalCh:
|
case signal := <-globalServiceSignalCh:
|
||||||
switch signal {
|
switch {
|
||||||
case serviceStatus:
|
case signal&serviceRestart == serviceRestart:
|
||||||
// Ignore this at the moment.
|
|
||||||
case serviceRestart:
|
|
||||||
logger.Info("Restarting on service signal")
|
logger.Info("Restarting on service signal")
|
||||||
stop := stopProcess()
|
stop := stopProcess()
|
||||||
rerr := restartProcess()
|
rerr := restartProcess()
|
||||||
logger.LogIf(context.Background(), rerr)
|
logger.LogIf(context.Background(), rerr)
|
||||||
exit(stop && rerr == nil)
|
exit(stop && rerr == nil)
|
||||||
case serviceStop:
|
case signal&serviceStop == serviceStop:
|
||||||
logger.Info("Stopping on service signal")
|
logger.Info("Stopping on service signal")
|
||||||
exit(stopProcess())
|
exit(stopProcess())
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -30,45 +31,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/inconshreveable/go-update"
|
"github.com/inconshreveable/go-update"
|
||||||
"github.com/minio/cli"
|
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
_ "github.com/minio/sha256-simd" // Needed for sha256 hash verifier.
|
_ "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 (
|
const (
|
||||||
minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z"
|
minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z"
|
||||||
minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH
|
minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH
|
||||||
@ -298,38 +266,58 @@ func getUserAgent(mode string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downloadReleaseURL(releaseChecksumURL string, timeout time.Duration, mode string) (content string, err error) {
|
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 {
|
if err != nil {
|
||||||
return content, err
|
return content, AdminError{
|
||||||
|
Code: AdminUpdateUnexpectedFailure,
|
||||||
|
Message: err.Error(),
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", getUserAgent(mode))
|
req.Header.Set("User-Agent", getUserAgent(mode))
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{Transport: getUpdateTransport(timeout)}
|
||||||
Timeout: timeout,
|
|
||||||
Transport: &http.Transport{
|
|
||||||
// need to close connection after usage.
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
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 {
|
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)
|
defer xhttp.DrainBody(resp.Body)
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
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)
|
contentBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
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.
|
// DownloadReleaseData - downloads release data from minio official server.
|
||||||
@ -338,6 +326,7 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e
|
|||||||
if runtime.GOOS == globalWindowsOSName {
|
if runtime.GOOS == globalWindowsOSName {
|
||||||
releaseURLs = minioReleaseWindowsInfoURLs
|
releaseURLs = minioReleaseWindowsInfoURLs
|
||||||
}
|
}
|
||||||
|
|
||||||
return func() (data string, err error) {
|
return func() (data string, err error) {
|
||||||
for _, url := range releaseURLs {
|
for _, url := range releaseURLs {
|
||||||
data, err = downloadReleaseURL(url, timeout, mode)
|
data, err = downloadReleaseURL(url, timeout, mode)
|
||||||
@ -345,7 +334,7 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data, fmt.Errorf("Failed to fetch release URL - last error: %s", err)
|
return data, err
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,6 +374,27 @@ func parseReleaseData(data string) (sha256Hex string, releaseTime time.Time, err
|
|||||||
return sha256Hex, releaseTime, 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) {
|
func getLatestReleaseTime(timeout time.Duration, mode string) (sha256Hex string, releaseTime time.Time, err error) {
|
||||||
data, err := DownloadReleaseData(timeout, mode)
|
data, err := DownloadReleaseData(timeout, mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -450,21 +460,39 @@ func getUpdateInfo(timeout time.Duration, mode string) (updateMsg string, sha256
|
|||||||
return prepareUpdateMessage(downloadURL, older), sha256Hex, currentReleaseTime, latestReleaseTime, nil
|
return prepareUpdateMessage(downloadURL, older), sha256Hex, currentReleaseTime, latestReleaseTime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateStatusMsg string, err error) {
|
func doUpdate(sha256Hex string, mode string, latestReleaseTime time.Time, ok bool) (err error) {
|
||||||
if !ok {
|
|
||||||
updateStatusMsg = colorRedBold("MinIO update to version RELEASE.%s canceled.",
|
|
||||||
latestReleaseTime.Format(minioReleaseTagTimeLayout))
|
|
||||||
return updateStatusMsg, nil
|
|
||||||
}
|
|
||||||
var sha256Sum []byte
|
var sha256Sum []byte
|
||||||
sha256Sum, err = hex.DecodeString(sha256Hex)
|
sha256Sum, err = hex.DecodeString(sha256Hex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return updateStatusMsg, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.Get(getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime)))
|
clnt := &http.Client{Transport: getUpdateTransport(30 * time.Second)}
|
||||||
|
req, err := http.NewRequest(http.MethodGet, getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime)), nil)
|
||||||
if err != 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)
|
defer xhttp.DrainBody(resp.Body)
|
||||||
|
|
||||||
@ -475,73 +503,19 @@ func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateSta
|
|||||||
Checksum: sha256Sum,
|
Checksum: sha256Sum,
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return updateStatusMsg, err
|
if os.IsPermission(err) {
|
||||||
}
|
return AdminError{
|
||||||
|
Code: AdminUpdateApplyFailure,
|
||||||
return colorGreenBold("MinIO updated to version RELEASE.%s successfully.",
|
Message: err.Error(),
|
||||||
latestReleaseTime.Format(minioReleaseTagTimeLayout)), nil
|
StatusCode: http.StatusForbidden,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Confirm continues prompting until the input is boolean-ish.
|
return AdminError{
|
||||||
func confirm(prompt string, args ...interface{}) bool {
|
Code: AdminUpdateApplyFailure,
|
||||||
for {
|
Message: err.Error(),
|
||||||
var s string
|
StatusCode: http.StatusInternalServerError,
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return nil
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -41,12 +41,13 @@ func main() {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
| Service operations | Info operations | Healing operations | Config operations | Top operations | IAM operations | Misc |
|
| 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) | |
|
| [`ServiceStatus`](#ServiceStatus) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`TopLocks`](#TopLocks) | [`AddUser`](#AddUser) | |
|
||||||
| [`ServiceSendAction`](#ServiceSendAction) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) |
|
| [`ServiceRestart`](#ServiceRestart) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) |
|
||||||
| [`Trace`](#Trace) | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | [`GetConfigKeys`](#GetConfigKeys) | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) |
|
| [`ServiceStop`](#ServiceStop) | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | [`GetConfigKeys`](#GetConfigKeys) | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) |
|
||||||
| | | | [`SetConfigKeys`](#SetConfigKeys) | | [`AddCannedPolicy`](#AddCannedPolicy) | |
|
| [`ServiceUpdate`](#ServiceUpdate) | | | [`SetConfigKeys`](#SetConfigKeys) | | [`AddCannedPolicy`](#AddCannedPolicy) | |
|
||||||
|
| [`ServiceTrace`](#ServiceTrace) | | | | | | |
|
||||||
|
|
||||||
|
|
||||||
## 1. Constructor
|
## 1. Constructor
|
||||||
@ -64,25 +65,7 @@ __Parameters__
|
|||||||
| `secretAccessKey` | _string_ | Secret key for the object storage endpoint. |
|
| `secretAccessKey` | _string_ | Secret key for the object storage endpoint. |
|
||||||
| `ssl` | _bool_ | Set this value to 'true' to enable secure (HTTPS) access. |
|
| `ssl` | _bool_ | Set this value to 'true' to enable secure (HTTPS) access. |
|
||||||
|
|
||||||
## 2. Admin API Version
|
## 2. Service operations
|
||||||
|
|
||||||
<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
|
|
||||||
|
|
||||||
<a name="ServiceStatus"></a>
|
<a name="ServiceStatus"></a>
|
||||||
### ServiceStatus() (ServiceStatusMetadata, error)
|
### ServiceStatus() (ServiceStatusMetadata, error)
|
||||||
@ -111,25 +94,73 @@ Fetch service status, replies disk space used, backend type and total disks offl
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<a name="ServiceSendAction"></a>
|
<a name="ServiceRestart"></a>
|
||||||
### ServiceSendAction(act ServiceActionValue) (error)
|
### ServiceRestart() error
|
||||||
Sends a service action command to service - possible actions are restarting and stopping the server.
|
Sends a service action restart command to MinIO server.
|
||||||
|
|
||||||
__Example__
|
__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
|
<a name="ServiceStop"></a>
|
||||||
// to restart
|
### ServiceStop() error
|
||||||
st, err := madmClnt.ServiceSendAction(ServiceActionValueRestart)
|
Sends a service action stop command to MinIO server.
|
||||||
// or to stop
|
|
||||||
// st, err := madmClnt.ServiceSendAction(ServiceActionValueStop)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
log.Printf("Success")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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="ServiceUpdate"></a>
|
||||||
|
### ServiceUpdate() (ServiceUpdateStatus, error)
|
||||||
|
Sends a service action update command to MinIO server, to update MinIO server to latest release.
|
||||||
|
|
||||||
|
__Example__
|
||||||
|
|
||||||
|
```go
|
||||||
|
// To update the service, update and restarts all the servers in the cluster.
|
||||||
|
us, err := madmClnt.ServiceUpdate()
|
||||||
|
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="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>
|
<a name="ServerInfo"></a>
|
||||||
### ServerInfo() ([]ServerInfo, error)
|
### ServerInfo() ([]ServerInfo, error)
|
||||||
@ -262,7 +293,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.Mem` | _uint64_ | The total number of bytes obtained from the OS |
|
||||||
| `mem.Usage.Error` | _string_ | Error (if any) encountered while accesing the CPU info |
|
| `mem.Usage.Error` | _string_ | Error (if any) encountered while accesing the CPU info |
|
||||||
|
|
||||||
## 6. Heal operations
|
## 5. Heal operations
|
||||||
|
|
||||||
<a name="Heal"></a>
|
<a name="Heal"></a>
|
||||||
### Heal(bucket, prefix string, healOpts HealOpts, clientToken string, forceStart bool, forceStop bool) (start HealStartSuccess, status HealTaskStatus, err error)
|
### Heal(bucket, prefix string, healOpts HealOpts, clientToken string, forceStart bool, forceStop bool) (start HealStartSuccess, status HealTaskStatus, err error)
|
||||||
@ -327,7 +358,7 @@ __Example__
|
|||||||
| `DiskInfo.AvailableOn` | _[]int_ | List of disks on which the healed entity is present and healthy |
|
| `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 |
|
| `DiskInfo.HealedOn` | _[]int_ | List of disks on which the healed entity was restored |
|
||||||
|
|
||||||
## 7. Config operations
|
## 6. Config operations
|
||||||
|
|
||||||
<a name="GetConfig"></a>
|
<a name="GetConfig"></a>
|
||||||
### GetConfig() ([]byte, error)
|
### GetConfig() ([]byte, error)
|
||||||
@ -405,7 +436,7 @@ __Example__
|
|||||||
log.Println("New configuration successfully set")
|
log.Println("New configuration successfully set")
|
||||||
```
|
```
|
||||||
|
|
||||||
## 8. Top operations
|
## 7. Top operations
|
||||||
|
|
||||||
<a name="TopLocks"></a>
|
<a name="TopLocks"></a>
|
||||||
### TopLocks() (LockEntries, error)
|
### TopLocks() (LockEntries, error)
|
||||||
@ -427,7 +458,7 @@ __Example__
|
|||||||
log.Println("TopLocks received successfully: ", string(out))
|
log.Println("TopLocks received successfully: ", string(out))
|
||||||
```
|
```
|
||||||
|
|
||||||
## 9. IAM operations
|
## 8. IAM operations
|
||||||
|
|
||||||
<a name="AddCannedPolicy"></a>
|
<a name="AddCannedPolicy"></a>
|
||||||
### AddCannedPolicy(policyName string, policy string) error
|
### AddCannedPolicy(policyName string, policy string) error
|
||||||
@ -483,7 +514,7 @@ __Example__
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 10. Misc operations
|
## 9. Misc operations
|
||||||
|
|
||||||
<a name="StartProfiling"></a>
|
<a name="StartProfiling"></a>
|
||||||
### StartProfiling(profiler string) error
|
### StartProfiling(profiler string) error
|
||||||
@ -537,22 +568,3 @@ __Example__
|
|||||||
|
|
||||||
log.Println("Profiling data successfully downloaded.")
|
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
|
|
||||||
}
|
|
@ -43,7 +43,7 @@ func main() {
|
|||||||
// in the minio cluster.
|
// in the minio cluster.
|
||||||
allTrace := false
|
allTrace := false
|
||||||
errTrace := false
|
errTrace := false
|
||||||
traceCh := madmClnt.Trace(allTrace, errTrace, doneCh)
|
traceCh := madmClnt.ServiceTrace(allTrace, errTrace, doneCh)
|
||||||
for traceInfo := range traceCh {
|
for traceInfo := range traceCh {
|
||||||
if traceInfo.Err != nil {
|
if traceInfo.Err != nil {
|
||||||
fmt.Println(traceInfo.Err)
|
fmt.Println(traceInfo.Err)
|
@ -21,7 +21,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
trace "github.com/minio/minio/pkg/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServerVersion - server version
|
// ServerVersion - server version
|
||||||
@ -30,72 +34,134 @@ type ServerVersion struct {
|
|||||||
CommitID string `json:"commitID"`
|
CommitID string `json:"commitID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServiceUpdateStatus - contains the response of service update API
|
||||||
|
type ServiceUpdateStatus struct {
|
||||||
|
CurrentVersion string `json:"currentVersion"`
|
||||||
|
UpdatedVersion string `json:"updatedVersion"`
|
||||||
|
}
|
||||||
|
|
||||||
// ServiceStatus - contains the response of service status API
|
// ServiceStatus - contains the response of service status API
|
||||||
type ServiceStatus struct {
|
type ServiceStatus struct {
|
||||||
ServerVersion ServerVersion `json:"serverVersion"`
|
ServerVersion ServerVersion `json:"serverVersion"`
|
||||||
Uptime time.Duration `json:"uptime"`
|
Uptime time.Duration `json:"uptime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceStatus - Connect to a minio server and call Service Status
|
// ServiceStatus - Returns current server uptime and current
|
||||||
// Management API to fetch server's storage information represented by
|
// running version of MinIO server.
|
||||||
// ServiceStatusMetadata structure
|
|
||||||
func (adm *AdminClient) ServiceStatus() (ss ServiceStatus, err error) {
|
func (adm *AdminClient) ServiceStatus() (ss ServiceStatus, err error) {
|
||||||
// Request API to GET service status
|
respBytes, err := adm.serviceCallAction(ServiceActionStatus)
|
||||||
resp, err := adm.executeMethod("GET", requestData{relPath: "/v1/service"})
|
|
||||||
defer closeResponse(resp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ss, err
|
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)
|
err = json.Unmarshal(respBytes, &ss)
|
||||||
return ss, err
|
return ss, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceActionValue - type to restrict service-action values
|
// ServiceRestart - restarts the MinIO cluster
|
||||||
type ServiceActionValue string
|
func (adm *AdminClient) ServiceRestart() error {
|
||||||
|
_, err := adm.serviceCallAction(ServiceActionRestart)
|
||||||
const (
|
return err
|
||||||
// ServiceActionValueRestart represents restart action
|
|
||||||
ServiceActionValueRestart ServiceActionValue = "restart"
|
|
||||||
// ServiceActionValueStop represents stop action
|
|
||||||
ServiceActionValueStop = "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
|
// ServiceStop - stops the MinIO cluster
|
||||||
// MinIO server
|
func (adm *AdminClient) ServiceStop() error {
|
||||||
func (adm *AdminClient) ServiceSendAction(action ServiceActionValue) error {
|
_, err := adm.serviceCallAction(ServiceActionStop)
|
||||||
body, err := json.Marshal(ServiceAction{action})
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceUpdate - updates and restarts the MinIO cluster to latest version.
|
||||||
|
func (adm *AdminClient) ServiceUpdate() (us ServiceUpdateStatus, err error) {
|
||||||
|
respBytes, err := adm.serviceCallAction(ServiceActionUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return us, err
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(respBytes, &us)
|
||||||
|
return us, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceAction - type to restrict service-action values
|
||||||
|
type ServiceAction string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ServiceActionStatus represents status action
|
||||||
|
ServiceActionStatus ServiceAction = "status"
|
||||||
|
// ServiceActionRestart represents restart action
|
||||||
|
ServiceActionRestart = "restart"
|
||||||
|
// ServiceActionStop represents stop action
|
||||||
|
ServiceActionStop = "stop"
|
||||||
|
// ServiceActionUpdate represents update action
|
||||||
|
ServiceActionUpdate = "update"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
// Request API to Restart server
|
||||||
resp, err := adm.executeMethod("POST", requestData{
|
resp, err := adm.executeMethod("POST", requestData{
|
||||||
relPath: "/v1/service",
|
relPath: "/v1/service",
|
||||||
content: body,
|
queryValues: queryValues,
|
||||||
})
|
})
|
||||||
defer closeResponse(resp)
|
defer closeResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
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
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user