From 83d4c5763c3ef97a1ce61f079b41e4691dcaa1dc Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 28 Aug 2019 15:04:43 -0700 Subject: [PATCH] Decouple ServiceUpdate to ServerUpdate to be more native (#8138) The change now is to ensure that we take custom URL as well for updating the deployment, this is required for hotfix deliveries for certain deployments - other than the community release. This commit changes the previous work d65a2c672518a8af5f37f07c3abf18222df1839a with newer set of requirements. Also deprecates PeerUptime() --- cmd/admin-handlers.go | 188 +++++++++++--------- cmd/admin-handlers_test.go | 28 +-- cmd/admin-router.go | 4 +- cmd/common-main.go | 2 +- cmd/notification.go | 15 ++ cmd/peer-rest-client.go | 18 ++ cmd/peer-rest-common.go | 30 ++-- cmd/peer-rest-server.go | 91 ++++------ cmd/service.go | 4 +- cmd/signals.go | 6 +- cmd/{update-main.go => update.go} | 40 +++-- cmd/{update-main_test.go => update_test.go} | 1 + pkg/madmin/README.md | 58 +++--- pkg/madmin/examples/service-status.go | 44 ----- pkg/madmin/service-commands.go | 48 +---- pkg/madmin/update-commands.go | 59 ++++++ 16 files changed, 319 insertions(+), 317 deletions(-) rename cmd/{update-main.go => update.go} (93%) rename cmd/{update-main_test.go => update_test.go} (98%) delete mode 100644 pkg/madmin/examples/service-status.go create mode 100644 pkg/madmin/update-commands.go diff --git a/cmd/admin-handlers.go b/cmd/admin-handlers.go index 213f74f6d..a1eefc571 100644 --- a/cmd/admin-handlers.go +++ b/cmd/admin-handlers.go @@ -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. @@ -25,7 +25,10 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "os" + "path" + "runtime" "sort" "strconv" "strings" @@ -66,18 +69,27 @@ const ( mgmtForceStop = "forceStop" ) -func updateServer() (us madmin.ServiceUpdateStatus, err error) { +func updateServer(updateURL, sha256Hex string, latestReleaseTime time.Time) (us madmin.ServerUpdateStatus, err error) { minioMode := getMinioMode() - updateMsg, sha256Hex, _, latestReleaseTime, err := getUpdateInfo(updateTimeout, minioMode) - if err != nil { - return us, err + // 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 updateMsg == "" { - us.CurrentVersion = Version - us.UpdatedVersion = Version - return us, nil - } - if err = doUpdate(sha256Hex, minioMode, latestReleaseTime, true); err != nil { + if err = doUpdate(updateURL, sha256Hex, minioMode); err != nil { return us, err } us.CurrentVersion = Version @@ -85,12 +97,88 @@ func updateServer() (us madmin.ServiceUpdateStatus, err error) { return us, nil } -// ServiceActionHandler - POST /minio/admin/v1/service -// Body: {"action": } +// ServerUpdateHandler - POST /minio/admin/v1/update?updateURL={updateURL} // ---------- -// restarts/updates/stops minio server gracefully. In a distributed setup, -// restarts/updates/stops all the servers in the cluster. Also asks for -// server version and uptime. +// 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 + } + + 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 + } + + // 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 <- serviceRestart + } +} + +// ServiceActionHandler - POST /minio/admin/v1/service?action={action} +// ---------- +// restarts/stops minio server gracefully. In a distributed setup, func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "ServiceAction") @@ -108,80 +196,12 @@ func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Re serviceSig = serviceRestart case madmin.ServiceActionStop: 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: logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action)) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL) return } - if serviceSig&serviceUpdate == serviceUpdate { - 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. for _, nerr := range globalNotificationSys.SignalService(serviceSig) { if nerr.Err != nil { diff --git a/cmd/admin-handlers_test.go b/cmd/admin-handlers_test.go index c6690834b..2beb16597 100644 --- a/cmd/admin-handlers_test.go +++ b/cmd/admin-handlers_test.go @@ -349,8 +349,7 @@ func initTestXLObjLayer() (ObjectLayer, []string, error) { type cmdType int const ( - statusCmd cmdType = iota - restartCmd + restartCmd cmdType = iota stopCmd ) @@ -358,14 +357,12 @@ const ( // 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) toServiceAction() madmin.ServiceAction { @@ -374,10 +371,8 @@ func (c cmdType) toServiceAction() madmin.ServiceAction { return madmin.ServiceActionRestart case stopCmd: return madmin.ServiceActionStop - case statusCmd: - return madmin.ServiceActionStatus } - return madmin.ServiceActionStatus + return madmin.ServiceActionRestart } // testServiceSignalReceiver - Helper function that simulates a @@ -449,28 +444,11 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) { t.Errorf("Expected to receive %d status code but received %d. Body (%s)", http.StatusOK, rec.Code, string(resp)) } - 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) - } - } // Wait until testServiceSignalReceiver() called in a goroutine quits. 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) diff --git a/cmd/admin-router.go b/cmd/admin-router.go index 174561246..fd3ec0ad6 100644 --- a/cmd/admin-router.go +++ b/cmd/admin-router.go @@ -42,8 +42,10 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool) /// Service operations - // Get status, update, restart and stop MinIO service. + // 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)) diff --git a/cmd/common-main.go b/cmd/common-main.go index 79f4e8468..e6984edb7 100644 --- a/cmd/common-main.go +++ b/cmd/common-main.go @@ -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))) } } } diff --git a/cmd/notification.go b/cmd/notification.go index 98137fca2..c2b6ed998 100644 --- a/cmd/notification.go +++ b/cmd/notification.go @@ -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)) diff --git a/cmd/peer-rest-client.go b/cmd/peer-rest-client.go index d303f1dea..c32174901 100644 --- a/cmd/peer-rest-client.go +++ b/cmd/peer-rest-client.go @@ -503,6 +503,24 @@ 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) diff --git a/cmd/peer-rest-common.go b/cmd/peer-rest-common.go index bf5e27023..27de8f01f 100644 --- a/cmd/peer-rest-common.go +++ b/cmd/peer-rest-common.go @@ -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" ) diff --git a/cmd/peer-rest-server.go b/cmd/peer-rest-server.go index 967965544..38f7cc5fa 100644 --- a/cmd/peer-rest-server.go +++ b/cmd/peer-rest-server.go @@ -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. @@ -831,22 +819,10 @@ func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Req } signal := serviceSignal(si) defer w.(http.Flusher).Flush() - switch { - 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: + switch signal { + case serviceRestart: + globalServiceSignalCh <- signal + case serviceStop: globalServiceSignalCh <- signal default: s.writeErrorResponse(w, errUnsupportedSignal) @@ -954,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)...) diff --git a/cmd/service.go b/cmd/service.go index 81514c2fc..85cd716e7 100644 --- a/cmd/service.go +++ b/cmd/service.go @@ -26,10 +26,8 @@ import ( type serviceSignal int const ( - serviceStatus serviceSignal = iota // Gets status about the service. - serviceRestart // Restarts the service. + serviceRestart serviceSignal = iota // Restarts the server. serviceStop // Stops the server. - serviceUpdate // Updates the server. // Add new service requests here. ) diff --git a/cmd/signals.go b/cmd/signals.go index 937876233..d0f73e344 100644 --- a/cmd/signals.go +++ b/cmd/signals.go @@ -77,14 +77,14 @@ func handleSignals() { logger.Info("Exiting on signal: %s", strings.ToUpper(osSignal.String())) exit(stopProcess()) case signal := <-globalServiceSignalCh: - switch { - case signal&serviceRestart == serviceRestart: + switch signal { + case serviceRestart: logger.Info("Restarting on service signal") stop := stopProcess() rerr := restartProcess() logger.LogIf(context.Background(), rerr) exit(stop && rerr == nil) - case signal&serviceStop == serviceStop: + case serviceStop: logger.Info("Stopping on service signal") exit(stopProcess()) } diff --git a/cmd/update-main.go b/cmd/update.go similarity index 93% rename from cmd/update-main.go rename to cmd/update.go index 64313f7f3..c8769cb0b 100644 --- a/cmd/update-main.go +++ b/cmd/update.go @@ -77,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` @@ -343,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. // // 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) @@ -356,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. + 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) } @@ -460,15 +474,19 @@ func getUpdateInfo(timeout time.Duration, mode string) (updateMsg string, sha256 return prepareUpdateMessage(downloadURL, older), sha256Hex, currentReleaseTime, latestReleaseTime, nil } -func doUpdate(sha256Hex string, mode string, latestReleaseTime time.Time, ok bool) (err error) { +func doUpdate(updateURL, sha256Hex, mode string) (err error) { var sha256Sum []byte sha256Sum, err = hex.DecodeString(sha256Hex) if err != nil { - return err + return AdminError{ + Code: AdminUpdateUnexpectedFailure, + Message: err.Error(), + StatusCode: http.StatusInternalServerError, + } } clnt := &http.Client{Transport: getUpdateTransport(30 * time.Second)} - req, err := http.NewRequest(http.MethodGet, getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime)), nil) + req, err := http.NewRequest(http.MethodGet, updateURL, nil) if err != nil { return AdminError{ Code: AdminUpdateUnexpectedFailure, diff --git a/cmd/update-main_test.go b/cmd/update_test.go similarity index 98% rename from cmd/update-main_test.go rename to cmd/update_test.go index 3934b3c4c..19c63b880 100644 --- a/cmd/update-main_test.go +++ b/cmd/update_test.go @@ -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 { diff --git a/pkg/madmin/README.md b/pkg/madmin/README.md index 7fd7d95d3..5b0dc9756 100644 --- a/pkg/madmin/README.md +++ b/pkg/madmin/README.md @@ -31,25 +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) | | -| [`ServiceRestart`](#ServiceRestart) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) | -| [`ServiceStop`](#ServiceStop) | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | [`GetConfigKeys`](#GetConfigKeys) | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) | -| [`ServiceUpdate`](#ServiceUpdate) | | | [`SetConfigKeys`](#SetConfigKeys) | | [`AddCannedPolicy`](#AddCannedPolicy) | | -| [`ServiceTrace`](#ServiceTrace) | | | | | | | - - +| 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 @@ -124,23 +124,6 @@ Sends a service action stop command to MinIO server. log.Println("Success") ``` - -### 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) - } -``` - ### ServiceTrace(allTrace bool, doneCh <-chan struct{}) <-chan TraceInfo Enable HTTP request tracing on all nodes in a MinIO cluster @@ -159,7 +142,6 @@ __Example__ } ``` - ## 3. Info operations @@ -516,6 +498,24 @@ __Example__ ## 9. Misc operations + +### 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) + } +``` + ### StartProfiling(profiler string) error Ask all nodes to start profiling using the specified profiler mode diff --git a/pkg/madmin/examples/service-status.go b/pkg/madmin/examples/service-status.go deleted file mode 100644 index cec346802..000000000 --- a/pkg/madmin/examples/service-status.go +++ /dev/null @@ -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 (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 { - log.Fatalln(err) - } - - st, err := madmClnt.ServiceStatus() - if err != nil { - log.Fatalln(err) - } - log.Println(st) -} diff --git a/pkg/madmin/service-commands.go b/pkg/madmin/service-commands.go index ab20f8ddd..6e547c200 100644 --- a/pkg/madmin/service-commands.go +++ b/pkg/madmin/service-commands.go @@ -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. @@ -23,40 +23,10 @@ import ( "net/http" "net/url" "strconv" - "time" trace "github.com/minio/minio/pkg/trace" ) -// ServerVersion - server version -type ServerVersion struct { - Version string `json:"version"` - 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 -type ServiceStatus struct { - ServerVersion ServerVersion `json:"serverVersion"` - Uptime time.Duration `json:"uptime"` -} - -// ServiceStatus - Returns current server uptime and current -// running version of MinIO server. -func (adm *AdminClient) ServiceStatus() (ss ServiceStatus, err error) { - respBytes, err := adm.serviceCallAction(ServiceActionStatus) - if err != nil { - return ss, err - } - err = json.Unmarshal(respBytes, &ss) - return ss, err -} - // ServiceRestart - restarts the MinIO cluster func (adm *AdminClient) ServiceRestart() error { _, err := adm.serviceCallAction(ServiceActionRestart) @@ -69,28 +39,14 @@ func (adm *AdminClient) ServiceStop() error { 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 { - 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" + ServiceActionRestart ServiceAction = "restart" // ServiceActionStop represents stop action ServiceActionStop = "stop" - // ServiceActionUpdate represents update action - ServiceActionUpdate = "update" ) // serviceCallAction - call service restart/update/stop API. diff --git a/pkg/madmin/update-commands.go b/pkg/madmin/update-commands.go new file mode 100644 index 000000000..dce244024 --- /dev/null +++ b/pkg/madmin/update-commands.go @@ -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 +}