Skip to content

Commit

Permalink
admin: ServerInfo() returns info for each node (minio#4150)
Browse files Browse the repository at this point in the history
ServerInfo() will gather information from all nodes before returning
it back to the client.
  • Loading branch information
vadmeste authored and harshavardhana committed Apr 21, 2017
1 parent df34675 commit 83abad0
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 107 deletions.
77 changes: 34 additions & 43 deletions cmd/admin-handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"net/url"
"path"
"strconv"
"sync"
"time"
)

Expand Down Expand Up @@ -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
Expand All @@ -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.")
Expand Down
24 changes: 18 additions & 6 deletions cmd/admin-handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
51 changes: 37 additions & 14 deletions cmd/admin-rpc-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand Down
39 changes: 28 additions & 11 deletions cmd/admin-rpc-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions cmd/http-stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
8 changes: 6 additions & 2 deletions cmd/server-mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 83abad0

Please sign in to comment.