diff --git a/cmd/admin-handlers.go b/cmd/admin-handlers.go index b3773bdd3..8396e4e6b 100644 --- a/cmd/admin-handlers.go +++ b/cmd/admin-handlers.go @@ -26,6 +26,7 @@ import ( "net/url" "path" "strconv" + "sync" "time" ) @@ -229,14 +230,22 @@ type ServerHTTPStats struct { SuccessDELETEStats ServerHTTPMethodStats `json:"successDELETEs"` } -// ServerInfo holds the information that will be returned by ServerInfo API -type ServerInfo struct { +// ServerInfoData holds storage, connections and other +// information of a given server. +type ServerInfoData struct { StorageInfo StorageInfo `json:"storage"` ConnStats ServerConnStats `json:"network"` HTTPStats ServerHTTPStats `json:"http"` Properties ServerProperties `json:"server"` } +// ServerInfo holds server information result of one node +type ServerInfo struct { + Error error + Addr string + Data *ServerInfoData +} + // ServerInfoHandler - GET /?info // ---------- // Get server information @@ -248,55 +257,37 @@ func (adminAPI adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *htt return } - // Build storage info - objLayer := newObjectLayerFn() - if objLayer == nil { - writeErrorResponse(w, ErrServerNotInitialized, r.URL) - return - } - storage := objLayer.StorageInfo() + // Web service response + reply := make([]ServerInfo, len(globalAdminPeers)) - // Build list of enabled ARNs queues - var arns []string - for queueArn := range globalEventNotifier.GetAllExternalTargets() { - arns = append(arns, queueArn) - } + var wg sync.WaitGroup - // Fetch uptimes from all peers. This may fail to due to lack - // of read-quorum availability. - uptime, err := getPeerUptimes(globalAdminPeers) - if err != nil { - writeErrorResponse(w, toAPIErrorCode(err), r.URL) - errorIf(err, "Unable to get uptime from majority of servers.") - return - } + // Gather server information for all nodes + for i, p := range globalAdminPeers { + wg.Add(1) - // Build server properties information - properties := ServerProperties{ - Version: Version, - CommitID: CommitID, - Region: serverConfig.GetRegion(), - SQSARN: arns, - Uptime: uptime, - } + // Gather information from a peer in a goroutine + go func(idx int, peer adminPeer) { + defer wg.Done() - // Build network info - connStats := ServerConnStats{ - TotalInputBytes: globalConnStats.getTotalInputBytes(), - TotalOutputBytes: globalConnStats.getTotalOutputBytes(), - } - httpStats := globalHTTPStats.toServerHTTPStats() + // Initialize server info at index + reply[idx] = ServerInfo{Addr: peer.addr} - // Build the whole returned information - info := ServerInfo{ - StorageInfo: storage, - ConnStats: connStats, - HTTPStats: httpStats, - Properties: properties, + serverInfoData, err := peer.cmdRunner.ServerInfoData() + if err != nil { + errorIf(err, "Unable to get server info from %s.", peer.addr) + reply[idx].Error = err + return + } + + reply[idx].Data = &serverInfoData + }(i, p) } + wg.Wait() + // Marshal API response - jsonBytes, err := json.Marshal(info) + jsonBytes, err := json.Marshal(reply) if err != nil { writeErrorResponse(w, ErrInternalError, r.URL) errorIf(err, "Failed to marshal storage info into json.") diff --git a/cmd/admin-handlers_test.go b/cmd/admin-handlers_test.go index 4a6ad4d36..a8d984f9e 100644 --- a/cmd/admin-handlers_test.go +++ b/cmd/admin-handlers_test.go @@ -1300,17 +1300,29 @@ func TestAdminServerInfo(t *testing.T) { t.Errorf("Expected to succeed but failed with %d", rec.Code) } - result := ServerInfo{} - err = json.NewDecoder(rec.Body).Decode(&result) + results := []ServerInfo{} + err = json.NewDecoder(rec.Body).Decode(&results) if err != nil { t.Fatalf("Failed to decode set config result json %v", err) } - if result.StorageInfo.Free == 0 { - t.Error("Expected StorageInfo.Free to be non empty") + if len(results) == 0 { + t.Error("Expected at least one server info result") } - if result.Properties.Region != globalMinioDefaultRegion { - t.Errorf("Expected %s, got %s", globalMinioDefaultRegion, result.Properties.Region) + + for _, serverInfo := range results { + if len(serverInfo.Addr) == 0 { + t.Error("Expected server address to be non empty") + } + if serverInfo.Error != nil { + t.Errorf("Unexpected error = %v\n", serverInfo.Error) + } + if serverInfo.Data.StorageInfo.Free == 0 { + t.Error("Expected StorageInfo.Free to be non empty") + } + if serverInfo.Data.Properties.Region != globalMinioDefaultRegion { + t.Errorf("Expected %s, got %s", globalMinioDefaultRegion, serverInfo.Data.Properties.Region) + } } } diff --git a/cmd/admin-rpc-client.go b/cmd/admin-rpc-client.go index 52a27f4c3..13f65aa69 100644 --- a/cmd/admin-rpc-client.go +++ b/cmd/admin-rpc-client.go @@ -37,7 +37,7 @@ const ( serviceRestartRPC = "Admin.Restart" listLocksRPC = "Admin.ListLocks" reInitDisksRPC = "Admin.ReInitDisks" - uptimeRPC = "Admin.Uptime" + serverInfoDataRPC = "Admin.ServerInfoData" getConfigRPC = "Admin.GetConfig" writeTmpConfigRPC = "Admin.WriteTmpConfig" commitConfigRPC = "Admin.CommitConfig" @@ -59,7 +59,7 @@ type adminCmdRunner interface { Restart() error ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) ReInitDisks() error - Uptime() (time.Duration, error) + ServerInfoData() (ServerInfoData, error) GetConfig() ([]byte, error) WriteTmpConfig(tmpFileName string, configBytes []byte) error CommitConfig(tmpFileName string) error @@ -112,26 +112,48 @@ func (rc remoteAdminClient) ReInitDisks() error { return rc.Call(reInitDisksRPC, &args, &reply) } -// Uptime - Returns the uptime of this server. Timestamp is taken -// after object layer is initialized. -func (lc localAdminClient) Uptime() (time.Duration, error) { +// ServerInfoData - Returns the server info of this server. +func (lc localAdminClient) ServerInfoData() (ServerInfoData, error) { if globalBootTime.IsZero() { - return time.Duration(0), errServerNotInitialized + return ServerInfoData{}, errServerNotInitialized } - return UTCNow().Sub(globalBootTime), nil + // Build storage info + objLayer := newObjectLayerFn() + if objLayer == nil { + return ServerInfoData{}, errServerNotInitialized + } + storage := objLayer.StorageInfo() + + var arns []string + for queueArn := range globalEventNotifier.GetAllExternalTargets() { + arns = append(arns, queueArn) + } + + return ServerInfoData{ + StorageInfo: storage, + ConnStats: globalConnStats.toServerConnStats(), + HTTPStats: globalHTTPStats.toServerHTTPStats(), + Properties: ServerProperties{ + Uptime: UTCNow().Sub(globalBootTime), + Version: Version, + CommitID: CommitID, + SQSARN: arns, + Region: serverConfig.GetRegion(), + }, + }, nil } -// Uptime - returns the uptime of the server to which the RPC call is made. -func (rc remoteAdminClient) Uptime() (time.Duration, error) { +// ServerInfo - returns the server info of the server to which the RPC call is made. +func (rc remoteAdminClient) ServerInfoData() (ServerInfoData, error) { args := AuthRPCArgs{} - reply := UptimeReply{} - err := rc.Call(uptimeRPC, &args, &reply) + reply := ServerInfoDataReply{} + err := rc.Call(serverInfoDataRPC, &args, &reply) if err != nil { - return time.Duration(0), err + return ServerInfoData{}, err } - return reply.Uptime, nil + return reply.ServerInfoData, nil } // GetConfig - returns config.json of the local server. @@ -384,7 +406,8 @@ func getPeerUptimes(peers adminPeers) (time.Duration, error) { wg.Add(1) go func(idx int, peer adminPeer) { defer wg.Done() - uptimes[idx].uptime, uptimes[idx].err = peer.cmdRunner.Uptime() + serverInfoData, rpcErr := peer.cmdRunner.ServerInfoData() + uptimes[idx].uptime, uptimes[idx].err = serverInfoData.Properties.Uptime, rpcErr }(i, peer) } wg.Wait() diff --git a/cmd/admin-rpc-server.go b/cmd/admin-rpc-server.go index 5339cd7d0..25a03b310 100644 --- a/cmd/admin-rpc-server.go +++ b/cmd/admin-rpc-server.go @@ -53,10 +53,10 @@ type ListLocksReply struct { volLocks []VolumeLockInfo } -// UptimeReply - wraps the uptime response over RPC. -type UptimeReply struct { +// ServerInfoDataReply - wraps the server info response over RPC. +type ServerInfoDataReply struct { AuthRPCReply - Uptime time.Duration + ServerInfoData ServerInfoData } // ConfigReply - wraps the server config response over RPC. @@ -122,8 +122,8 @@ func (s *adminCmd) ReInitDisks(args *AuthRPCArgs, reply *AuthRPCReply) error { return nil } -// Uptime - returns the time when object layer was initialized on this server. -func (s *adminCmd) Uptime(args *AuthRPCArgs, reply *UptimeReply) error { +// ServerInfo - returns the server info when object layer was initialized on this server. +func (s *adminCmd) ServerInfoData(args *AuthRPCArgs, reply *ServerInfoDataReply) error { if err := args.IsAuthenticated(); err != nil { return err } @@ -132,12 +132,29 @@ func (s *adminCmd) Uptime(args *AuthRPCArgs, reply *UptimeReply) error { return errServerNotInitialized } - // N B The uptime is computed assuming that the system time is - // monotonic. This is not the case in time pkg in Go, see - // https://github.com/golang/go/issues/12914. This is expected - // to be fixed by go1.9. - *reply = UptimeReply{ - Uptime: UTCNow().Sub(globalBootTime), + // Build storage info + objLayer := newObjectLayerFn() + if objLayer == nil { + return errServerNotInitialized + } + storageInfo := objLayer.StorageInfo() + + var arns []string + for queueArn := range globalEventNotifier.GetAllExternalTargets() { + arns = append(arns, queueArn) + } + + reply.ServerInfoData = ServerInfoData{ + Properties: ServerProperties{ + Uptime: UTCNow().Sub(globalBootTime), + Version: Version, + CommitID: CommitID, + Region: serverConfig.GetRegion(), + SQSARN: arns, + }, + StorageInfo: storageInfo, + ConnStats: globalConnStats.toServerConnStats(), + HTTPStats: globalHTTPStats.toServerHTTPStats(), } return nil diff --git a/cmd/http-stats.go b/cmd/http-stats.go index ff917131d..9a8930e1c 100644 --- a/cmd/http-stats.go +++ b/cmd/http-stats.go @@ -52,6 +52,14 @@ func (s *ConnStats) getTotalOutputBytes() uint64 { return s.totalOutputBytes.Load() } +// Return connection stats (total input/output bytes) +func (s *ConnStats) toServerConnStats() ServerConnStats { + return ServerConnStats{ + TotalInputBytes: s.getTotalInputBytes(), + TotalOutputBytes: s.getTotalOutputBytes(), + } +} + // Prepare new ConnStats structure func newConnStats() *ConnStats { return &ConnStats{} diff --git a/cmd/server-mux.go b/cmd/server-mux.go index ed0cb2016..75839e26e 100644 --- a/cmd/server-mux.go +++ b/cmd/server-mux.go @@ -121,7 +121,9 @@ func (c *ConnMux) PeekProtocol() (string, error) { // bytes received from the client. func (c *ConnMux) Read(b []byte) (n int, err error) { // Update total incoming number of bytes. - defer globalConnStats.incInputBytes(n) + defer func() { + globalConnStats.incInputBytes(n) + }() n, err = c.peeker.Read(b) if err != nil { @@ -141,7 +143,9 @@ func (c *ConnMux) Read(b []byte) (n int, err error) { // keeps track of the total bytes written by the server. func (c *ConnMux) Write(b []byte) (n int, err error) { // Update total outgoing number of bytes. - defer globalConnStats.incOutputBytes(n) + defer func() { + globalConnStats.incOutputBytes(n) + }() // Call the conn write wrapper. return c.Conn.Write(b) diff --git a/pkg/madmin/API.md b/pkg/madmin/API.md index dc09b9978..8d6c3d99e 100644 --- a/pkg/madmin/API.md +++ b/pkg/madmin/API.md @@ -122,7 +122,30 @@ If successful restarts the running minio service, for distributed setup restarts ``` -## 3. Lock operations +## 3. Info operations + + +### ServerInfo() ([]ServerInfo, error) +Fetch all information for all cluster nodes, such as uptime, region, network statistics, etc.. + + + __Example__ + + ```go + + serversInfo, err := madmClnt.ServerInfo() + if err != nil { + log.Fatalln(err) + } + + for _, peerInfo := range serversInfo { + log.Printf("Node: %s, Info: %v\n", peerInfo.Addr, peerInfo.Data) + } + + ``` + + +## 4. Lock operations ### ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) @@ -154,7 +177,7 @@ __Example__ ``` -## 4. Heal operations +## 5. Heal operations ### ListObjectsHeal(bucket, prefix string, recursive bool, doneCh <-chan struct{}) (<-chan ObjectInfo, error) @@ -360,7 +383,7 @@ If upload is successfully healed returns nil, otherwise returns error indicating log.Println("Heal-upload result: ", healResult) ``` -## 5. Config operations +## 6. Config operations ### GetConfig() ([]byte, error) @@ -384,23 +407,6 @@ __Example__ log.Println("config received successfully: ", string(buf.Bytes())) ``` -## 6. Misc operations - - - -### SetCredentials() error -Set new credentials of a Minio setup. - -__Example__ - -``` go - err = madmClnt.SetCredentials("YOUR-NEW-ACCESSKEY", "YOUR-NEW-SECRETKEY") - if err != nil { - log.Fatalln(err) - } - log.Println("New credentials successfully set.") - -``` ### SetConfig(config io.Reader) (SetConfigResult, error) @@ -435,3 +441,22 @@ __Example__ } log.Println("SetConfig: ", string(buf.Bytes())) ``` + +## 7. Misc operations + + + +### SetCredentials() error +Set new credentials of a Minio setup. + +__Example__ + +``` go + err = madmClnt.SetCredentials("YOUR-NEW-ACCESSKEY", "YOUR-NEW-SECRETKEY") + if err != nil { + log.Fatalln(err) + } + log.Println("New credentials successfully set.") + +``` + diff --git a/pkg/madmin/info-commands.go b/pkg/madmin/info-commands.go index 1c41fdf80..01dcff234 100644 --- a/pkg/madmin/info-commands.go +++ b/pkg/madmin/info-commands.go @@ -74,17 +74,24 @@ type ServerConnStats struct { TotalOutputBytes uint64 `json:"received"` } -// ServerInfo holds the whole server information that will be -// returned by ServerInfo API. -type ServerInfo struct { +// ServerInfoData holds storage, connections and other +// information of a given server +type ServerInfoData struct { StorageInfo StorageInfo `json:"storage"` ConnStats ServerConnStats `json:"network"` Properties ServerProperties `json:"server"` } +// ServerInfo holds server information result of one node +type ServerInfo struct { + Error error `json:"error"` + Addr string `json:"addr"` + Data *ServerInfoData `json:"data"` +} + // ServerInfo - Connect to a minio server and call Server Info Management API // to fetch server's information represented by ServerInfo structure -func (adm *AdminClient) ServerInfo() (ServerInfo, error) { +func (adm *AdminClient) ServerInfo() ([]ServerInfo, error) { // Prepare web service request reqData := requestData{} reqData.queryValues = make(url.Values) @@ -94,26 +101,26 @@ func (adm *AdminClient) ServerInfo() (ServerInfo, error) { resp, err := adm.executeMethod("GET", reqData) defer closeResponse(resp) if err != nil { - return ServerInfo{}, err + return nil, err } // Check response http status code if resp.StatusCode != http.StatusOK { - return ServerInfo{}, httpRespToErrorResponse(resp) + return nil, httpRespToErrorResponse(resp) } // Unmarshal the server's json response - var info ServerInfo + var serversInfo []ServerInfo respBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - return ServerInfo{}, err + return nil, err } - err = json.Unmarshal(respBytes, &info) + err = json.Unmarshal(respBytes, &serversInfo) if err != nil { - return ServerInfo{}, err + return nil, err } - return info, nil + return serversInfo, nil }