Refactor cluster.About() data

This commit is contained in:
Ingo Oppermann
2023-07-14 11:22:08 +02:00
parent fd7354286e
commit cefd35f7da
11 changed files with 431 additions and 250 deletions

View File

@@ -90,7 +90,7 @@ coverage:
go tool cover -html=test/cover.out -o test/cover.html go tool cover -html=test/cover.out -o test/cover.html
## commit: Prepare code for commit (vet, fmt, test) ## commit: Prepare code for commit (vet, fmt, test)
commit: vet fmt lint test build commit: vet fmt lint test vulncheck build
@echo "No errors found. Ready for a commit." @echo "No errors found. Ready for a commit."
## release: Build a release binary of core ## release: Build a release binary of core

View File

@@ -1301,31 +1301,56 @@ func (c *cluster) applyCommand(cmd *store.Command) error {
return nil return nil
} }
type ClusterRaftServer struct { type ClusterRaft struct {
ID string
Address string Address string
Voter bool
Leader bool
}
type ClusterRaftStats struct {
State string State string
LastContact time.Duration LastContact time.Duration
NumPeers uint64 NumPeers uint64
LogTerm uint64
LogIndex uint64
} }
type ClusterRaft struct { type ClusterNodeResources struct {
Server []ClusterRaftServer IsThrottling bool // Whether this core is currently throttling
Stats ClusterRaftStats NCPU float64 // Number of CPU on this node
CPU float64 // Current CPU load, 0-100*ncpu
CPULimit float64 // Defined CPU load limit, 0-100*ncpu
Mem uint64 // Currently used memory in bytes
MemLimit uint64 // Defined memory limit in bytes
}
type ClusterNode struct {
ID string
Name string
Version string
Status string
Error error
Voter bool
Leader bool
Address string
CreatedAt time.Time
Uptime time.Duration
LastContact time.Duration
Latency time.Duration
Core ClusterNodeCore
Resources ClusterNodeResources
}
type ClusterNodeCore struct {
Address string
Status string
Error error
LastContact time.Duration
Latency time.Duration
} }
type ClusterAbout struct { type ClusterAbout struct {
ID string ID string
Name string
Leader bool
Address string Address string
ClusterAPIAddress string
CoreAPIAddress string
Raft ClusterRaft Raft ClusterRaft
Nodes []proxy.NodeAbout Nodes []ClusterNode
Version ClusterVersion Version ClusterVersion
Degraded bool Degraded bool
DegradedErr error DegradedErr error
@@ -1336,49 +1361,80 @@ func (c *cluster) About() (ClusterAbout, error) {
about := ClusterAbout{ about := ClusterAbout{
ID: c.id, ID: c.id,
Address: c.Address(),
Degraded: degraded, Degraded: degraded,
DegradedErr: degradedErr, DegradedErr: degradedErr,
Version: Version,
} }
if address, err := c.ClusterAPIAddress(""); err == nil { if address, err := c.ClusterAPIAddress(""); err == nil {
about.ClusterAPIAddress = address about.Address = address
}
if address, err := c.CoreAPIAddress(""); err == nil {
about.CoreAPIAddress = address
} }
stats := c.raft.Stats() stats := c.raft.Stats()
about.Raft.Stats.State = stats.State about.Raft.Address = stats.Address
about.Raft.Stats.LastContact = stats.LastContact about.Raft.State = stats.State
about.Raft.Stats.NumPeers = stats.NumPeers about.Raft.LastContact = stats.LastContact
about.Raft.NumPeers = stats.NumPeers
about.Raft.LogIndex = stats.LogIndex
about.Raft.LogTerm = stats.LogTerm
servers, err := c.raft.Servers() servers, err := c.raft.Servers()
if err != nil { if err != nil {
c.logger.Error().WithError(err).Log("Raft configuration") c.logger.Warn().WithError(err).Log("Raft configuration")
return ClusterAbout{}, err
} }
for _, server := range servers { serversMap := map[string]raft.Server{}
node := ClusterRaftServer{
ID: server.ID, for _, s := range servers {
Address: server.Address, serversMap[s.ID] = s
Voter: server.Voter,
Leader: server.Leader,
} }
about.Raft.Server = append(about.Raft.Server, node) c.nodesLock.Lock()
for id, node := range c.nodes {
nodeAbout := node.About()
node := ClusterNode{
ID: id,
Name: nodeAbout.Name,
Version: nodeAbout.Version,
Status: nodeAbout.Status,
Error: nodeAbout.Error,
Address: nodeAbout.Address,
LastContact: nodeAbout.LastContact,
Latency: nodeAbout.Latency,
CreatedAt: nodeAbout.Core.CreatedAt,
Uptime: nodeAbout.Core.Uptime,
Core: ClusterNodeCore{
Address: nodeAbout.Core.Address,
Status: nodeAbout.Core.Status,
Error: nodeAbout.Core.Error,
LastContact: nodeAbout.Core.LastContact,
Latency: nodeAbout.Core.Latency,
},
Resources: ClusterNodeResources{
IsThrottling: nodeAbout.Resources.IsThrottling,
NCPU: nodeAbout.Resources.NCPU,
CPU: nodeAbout.Resources.CPU,
CPULimit: nodeAbout.Resources.CPULimit,
Mem: nodeAbout.Resources.Mem,
MemLimit: nodeAbout.Resources.MemLimit,
},
} }
about.Version = Version if id == c.id {
about.Name = nodeAbout.Name
nodes := c.ProxyReader().ListNodes()
for _, node := range nodes {
about.Nodes = append(about.Nodes, node.About())
} }
if s, ok := serversMap[id]; ok {
node.Voter = s.Voter
node.Leader = s.Leader
}
about.Nodes = append(about.Nodes, node)
}
c.nodesLock.Unlock()
return about, nil return about, nil
} }

View File

@@ -16,6 +16,7 @@ import (
type Node interface { type Node interface {
Stop() error Stop() error
About() About
Version() string Version() string
IPs() []string IPs() []string
Status() (string, error) Status() (string, error)
@@ -42,7 +43,7 @@ type node struct {
lastContactErr error lastContactErr error
lastCoreContact time.Time lastCoreContact time.Time
lastCoreContactErr error lastCoreContactErr error
latency time.Duration latency float64
pingLock sync.RWMutex pingLock sync.RWMutex
runLock sync.Mutex runLock sync.Mutex
@@ -96,8 +97,9 @@ func (n *node) start(id string) error {
address, config, err := n.CoreEssentials() address, config, err := n.CoreEssentials()
n.proxyNode = proxy.NewNode(id, address, config) n.proxyNode = proxy.NewNode(id, address, config)
if err != nil {
n.lastCoreContactErr = err n.lastCoreContactErr = err
if err != nil {
go func(ctx context.Context) { go func(ctx context.Context) {
ticker := time.NewTicker(5 * time.Second) ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop() defer ticker.Stop()
@@ -143,6 +145,74 @@ func (n *node) Stop() error {
return nil return nil
} }
var maxLastContact time.Duration = 5 * time.Second
type AboutCore struct {
Address string
State string
StateError error
Status string
Error error
CreatedAt time.Time
Uptime time.Duration
LastContact time.Duration
Latency time.Duration
}
type About struct {
ID string
Name string
Version string
Address string
Status string
LastContact time.Duration
Latency time.Duration
Error error
Core AboutCore
Resources proxy.NodeResources
}
func (n *node) About() About {
a := About{
ID: n.id,
Version: n.Version(),
Address: n.address,
}
n.pingLock.RLock()
a.LastContact = time.Since(n.lastContact)
if a.LastContact > maxLastContact {
a.Status = "offline"
} else {
a.Status = "online"
}
a.Latency = time.Duration(n.latency * float64(time.Second))
a.Error = n.lastContactErr
coreError := n.lastCoreContactErr
n.pingLock.RUnlock()
about := n.CoreAbout()
a.Name = about.Name
a.Core.Address = about.Address
a.Core.State = about.State
a.Core.StateError = about.Error
a.Core.CreatedAt = about.CreatedAt
a.Core.Uptime = about.Uptime
a.Core.LastContact = time.Since(about.LastContact)
if a.Core.LastContact > maxLastContact {
a.Core.Status = "offline"
} else {
a.Core.Status = "online"
}
a.Core.Error = coreError
a.Core.Latency = about.Latency
a.Resources = about.Resources
return a
}
func (n *node) Version() string { func (n *node) Version() string {
n.pingLock.RLock() n.pingLock.RLock()
defer n.pingLock.RUnlock() defer n.pingLock.RUnlock()
@@ -159,7 +229,7 @@ func (n *node) Status() (string, error) {
defer n.pingLock.RUnlock() defer n.pingLock.RUnlock()
since := time.Since(n.lastContact) since := time.Since(n.lastContact)
if since > 5*time.Second { if since > maxLastContact {
return "offline", fmt.Errorf("the cluster API didn't respond for %s because: %w", since, n.lastContactErr) return "offline", fmt.Errorf("the cluster API didn't respond for %s because: %w", since, n.lastContactErr)
} }
@@ -171,7 +241,7 @@ func (n *node) CoreStatus() (string, error) {
defer n.pingLock.RUnlock() defer n.pingLock.RUnlock()
since := time.Since(n.lastCoreContact) since := time.Since(n.lastCoreContact)
if since > 5*time.Second { if since > maxLastContact {
return "offline", fmt.Errorf("the core API didn't respond for %s because: %w", since, n.lastCoreContactErr) return "offline", fmt.Errorf("the core API didn't respond for %s because: %w", since, n.lastCoreContactErr)
} }
@@ -211,6 +281,10 @@ func (n *node) CoreAPIAddress() (string, error) {
return n.client.CoreAPIAddress() return n.client.CoreAPIAddress()
} }
func (n *node) CoreAbout() proxy.NodeAbout {
return n.proxyNode.About()
}
func (n *node) Barrier(name string) (bool, error) { func (n *node) Barrier(name string) (bool, error) {
return n.client.Barrier(name) return n.client.Barrier(name)
} }
@@ -232,7 +306,8 @@ func (n *node) ping(ctx context.Context) {
n.pingLock.Lock() n.pingLock.Lock()
n.version = version n.version = version
n.lastContact = time.Now() n.lastContact = time.Now()
n.latency = time.Since(start) n.lastContactErr = nil
n.latency = n.latency*0.2 + time.Since(start).Seconds()*0.8
n.pingLock.Unlock() n.pingLock.Unlock()
} else { } else {
n.pingLock.Lock() n.pingLock.Lock()

View File

@@ -541,6 +541,7 @@ func (n *node) About() NodeAbout {
Name: about.Name, Name: about.Name,
Address: n.address, Address: n.address,
State: state.String(), State: state.String(),
Error: n.peerErr,
CreatedAt: createdAt, CreatedAt: createdAt,
Uptime: time.Since(createdAt), Uptime: time.Since(createdAt),
LastContact: n.lastContact, LastContact: n.lastContact,

View File

@@ -83,9 +83,12 @@ type Server struct {
} }
type Stats struct { type Stats struct {
Address string
State string State string
LastContact time.Duration LastContact time.Duration
NumPeers uint64 NumPeers uint64
LogTerm uint64
LogIndex uint64
} }
type Config struct { type Config struct {
@@ -194,7 +197,9 @@ func (r *raft) Servers() ([]Server, error) {
} }
func (r *raft) Stats() Stats { func (r *raft) Stats() Stats {
stats := Stats{} stats := Stats{
Address: r.raftAddress,
}
s := r.raft.Stats() s := r.raft.Stats()
@@ -219,6 +224,14 @@ func (r *raft) Stats() Stats {
stats.NumPeers = x stats.NumPeers = x
} }
if x, err := strconv.ParseUint(s["last_log_term"], 10, 64); err == nil {
stats.LogTerm = x
}
if x, err := strconv.ParseUint(s["last_log_index"], 10, 64); err == nil {
stats.LogIndex = x
}
return stats return stats
} }

View File

@@ -4437,12 +4437,6 @@ const docTemplate = `{
"address": { "address": {
"type": "string" "type": "string"
}, },
"cluster_api_address": {
"type": "string"
},
"core_api_address": {
"type": "string"
},
"degraded": { "degraded": {
"type": "boolean" "type": "boolean"
}, },
@@ -4452,6 +4446,12 @@ const docTemplate = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"leader": {
"type": "boolean"
},
"name": {
"type": "string"
},
"nodes": { "nodes": {
"type": "array", "type": "array",
"items": { "items": {
@@ -4500,31 +4500,65 @@ const docTemplate = `{
"address": { "address": {
"type": "string" "type": "string"
}, },
"core": {
"$ref": "#/definitions/api.ClusterNodeCore"
},
"created_at": { "created_at": {
"description": "RFC 3339",
"type": "string"
},
"error": {
"type": "string" "type": "string"
}, },
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_contact": { "last_contact_ms": {
"description": "unix timestamp", "type": "number"
"type": "integer"
}, },
"latency_ms": { "latency_ms": {
"description": "milliseconds",
"type": "number" "type": "number"
}, },
"leader": {
"type": "boolean"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
"resources": { "resources": {
"$ref": "#/definitions/api.ClusterNodeResources" "$ref": "#/definitions/api.ClusterNodeResources"
}, },
"state": { "status": {
"type": "string" "type": "string"
}, },
"uptime_seconds": { "uptime_seconds": {
"type": "integer" "type": "integer"
},
"version": {
"type": "string"
},
"voter": {
"type": "boolean"
}
}
},
"api.ClusterNodeCore": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"error": {
"type": "string"
},
"last_contact_ms": {
"type": "number"
},
"latency_ms": {
"type": "number"
},
"status": {
"type": "string"
} }
} }
}, },
@@ -4580,43 +4614,20 @@ const docTemplate = `{
} }
}, },
"api.ClusterRaft": { "api.ClusterRaft": {
"type": "object",
"properties": {
"server": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterRaftServer"
}
},
"stats": {
"$ref": "#/definitions/api.ClusterRaftStats"
}
}
},
"api.ClusterRaftServer": {
"type": "object", "type": "object",
"properties": { "properties": {
"address": { "address": {
"description": "raft address",
"type": "string" "type": "string"
}, },
"id": {
"type": "string"
},
"leader": {
"type": "boolean"
},
"voter": {
"type": "boolean"
}
}
},
"api.ClusterRaftStats": {
"type": "object",
"properties": {
"last_contact_ms": { "last_contact_ms": {
"type": "number" "type": "number"
}, },
"log_index": {
"type": "integer"
},
"log_term": {
"type": "integer"
},
"num_peers": { "num_peers": {
"type": "integer" "type": "integer"
}, },

View File

@@ -4429,12 +4429,6 @@
"address": { "address": {
"type": "string" "type": "string"
}, },
"cluster_api_address": {
"type": "string"
},
"core_api_address": {
"type": "string"
},
"degraded": { "degraded": {
"type": "boolean" "type": "boolean"
}, },
@@ -4444,6 +4438,12 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"leader": {
"type": "boolean"
},
"name": {
"type": "string"
},
"nodes": { "nodes": {
"type": "array", "type": "array",
"items": { "items": {
@@ -4492,31 +4492,65 @@
"address": { "address": {
"type": "string" "type": "string"
}, },
"core": {
"$ref": "#/definitions/api.ClusterNodeCore"
},
"created_at": { "created_at": {
"description": "RFC 3339",
"type": "string"
},
"error": {
"type": "string" "type": "string"
}, },
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_contact": { "last_contact_ms": {
"description": "unix timestamp", "type": "number"
"type": "integer"
}, },
"latency_ms": { "latency_ms": {
"description": "milliseconds",
"type": "number" "type": "number"
}, },
"leader": {
"type": "boolean"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
"resources": { "resources": {
"$ref": "#/definitions/api.ClusterNodeResources" "$ref": "#/definitions/api.ClusterNodeResources"
}, },
"state": { "status": {
"type": "string" "type": "string"
}, },
"uptime_seconds": { "uptime_seconds": {
"type": "integer" "type": "integer"
},
"version": {
"type": "string"
},
"voter": {
"type": "boolean"
}
}
},
"api.ClusterNodeCore": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"error": {
"type": "string"
},
"last_contact_ms": {
"type": "number"
},
"latency_ms": {
"type": "number"
},
"status": {
"type": "string"
} }
} }
}, },
@@ -4572,43 +4606,20 @@
} }
}, },
"api.ClusterRaft": { "api.ClusterRaft": {
"type": "object",
"properties": {
"server": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterRaftServer"
}
},
"stats": {
"$ref": "#/definitions/api.ClusterRaftStats"
}
}
},
"api.ClusterRaftServer": {
"type": "object", "type": "object",
"properties": { "properties": {
"address": { "address": {
"description": "raft address",
"type": "string" "type": "string"
}, },
"id": {
"type": "string"
},
"leader": {
"type": "boolean"
},
"voter": {
"type": "boolean"
}
}
},
"api.ClusterRaftStats": {
"type": "object",
"properties": {
"last_contact_ms": { "last_contact_ms": {
"type": "number" "type": "number"
}, },
"log_index": {
"type": "integer"
},
"log_term": {
"type": "integer"
},
"num_peers": { "num_peers": {
"type": "integer" "type": "integer"
}, },

View File

@@ -69,16 +69,16 @@ definitions:
properties: properties:
address: address:
type: string type: string
cluster_api_address:
type: string
core_api_address:
type: string
degraded: degraded:
type: boolean type: boolean
degraded_error: degraded_error:
type: string type: string
id: id:
type: string type: string
leader:
type: boolean
name:
type: string
nodes: nodes:
items: items:
$ref: '#/definitions/api.ClusterNode' $ref: '#/definitions/api.ClusterNode'
@@ -110,24 +110,46 @@ definitions:
properties: properties:
address: address:
type: string type: string
core:
$ref: '#/definitions/api.ClusterNodeCore'
created_at: created_at:
description: RFC 3339
type: string
error:
type: string type: string
id: id:
type: string type: string
last_contact: last_contact_ms:
description: unix timestamp
type: integer
latency_ms:
description: milliseconds
type: number type: number
latency_ms:
type: number
leader:
type: boolean
name: name:
type: string type: string
resources: resources:
$ref: '#/definitions/api.ClusterNodeResources' $ref: '#/definitions/api.ClusterNodeResources'
state: status:
type: string type: string
uptime_seconds: uptime_seconds:
type: integer type: integer
version:
type: string
voter:
type: boolean
type: object
api.ClusterNodeCore:
properties:
address:
type: string
error:
type: string
last_contact_ms:
type: number
latency_ms:
type: number
status:
type: string
type: object type: object
api.ClusterNodeFiles: api.ClusterNodeFiles:
properties: properties:
@@ -165,30 +187,15 @@ definitions:
type: string type: string
type: object type: object
api.ClusterRaft: api.ClusterRaft:
properties:
server:
items:
$ref: '#/definitions/api.ClusterRaftServer'
type: array
stats:
$ref: '#/definitions/api.ClusterRaftStats'
type: object
api.ClusterRaftServer:
properties: properties:
address: address:
description: raft address
type: string type: string
id:
type: string
leader:
type: boolean
voter:
type: boolean
type: object
api.ClusterRaftStats:
properties:
last_contact_ms: last_contact_ms:
type: number type: number
log_index:
type: integer
log_term:
type: integer
num_peers: num_peers:
type: integer type: integer
state: state:

View File

@@ -3,32 +3,31 @@ package api
import ( import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/datarhei/core/v16/cluster/proxy"
) )
type ClusterNode struct { type ClusterNode struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"`
Status string `json:"status"`
Error string `json:"error"`
Voter bool `json:"voter"`
Leader bool `json:"leader"`
Address string `json:"address"` Address string `json:"address"`
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at"` // RFC 3339
Uptime int64 `json:"uptime_seconds"` Uptime int64 `json:"uptime_seconds"`
LastContact int64 `json:"last_contact"` // unix timestamp LastContact float64 `json:"last_contact_ms"`
Latency float64 `json:"latency_ms"` // milliseconds Latency float64 `json:"latency_ms"`
State string `json:"state"` Core ClusterNodeCore `json:"core"`
Resources ClusterNodeResources `json:"resources"` Resources ClusterNodeResources `json:"resources"`
} }
func (n *ClusterNode) Marshal(about proxy.NodeAbout) { type ClusterNodeCore struct {
n.ID = about.ID Address string `json:"address"`
n.Name = about.Name Status string `json:"status"`
n.Address = about.Address Error string `json:"error"`
n.CreatedAt = about.CreatedAt.Format(time.RFC3339) LastContact float64 `json:"last_contact_ms"`
n.Uptime = int64(about.Uptime.Seconds()) Latency float64 `json:"latency_ms"`
n.LastContact = about.LastContact.Unix()
n.Latency = about.Latency.Seconds() * 1000
n.State = about.State
n.Resources = ClusterNodeResources(about.Resources)
} }
type ClusterNodeResources struct { type ClusterNodeResources struct {
@@ -40,34 +39,20 @@ type ClusterNodeResources struct {
MemLimit uint64 `json:"memory_limit_bytes"` // bytes MemLimit uint64 `json:"memory_limit_bytes"` // bytes
} }
type ClusterNodeFiles struct { type ClusterRaft struct {
LastUpdate int64 `json:"last_update"` // unix timestamp Address string `json:"address"`
Files map[string][]string `json:"files"`
}
type ClusterRaftServer struct {
ID string `json:"id"`
Address string `json:"address"` // raft address
Voter bool `json:"voter"`
Leader bool `json:"leader"`
}
type ClusterRaftStats struct {
State string `json:"state"` State string `json:"state"`
LastContact float64 `json:"last_contact_ms"` LastContact float64 `json:"last_contact_ms"`
NumPeers uint64 `json:"num_peers"` NumPeers uint64 `json:"num_peers"`
} LogTerm uint64 `json:"log_term"`
LogIndex uint64 `json:"log_index"`
type ClusterRaft struct {
Server []ClusterRaftServer `json:"server"`
Stats ClusterRaftStats `json:"stats"`
} }
type ClusterAbout struct { type ClusterAbout struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"`
Leader bool `json:"leader"`
Address string `json:"address"` Address string `json:"address"`
ClusterAPIAddress string `json:"cluster_api_address"`
CoreAPIAddress string `json:"core_api_address"`
Raft ClusterRaft `json:"raft"` Raft ClusterRaft `json:"raft"`
Nodes []ClusterNode `json:"nodes"` Nodes []ClusterNode `json:"nodes"`
Version string `json:"version"` Version string `json:"version"`
@@ -75,6 +60,11 @@ type ClusterAbout struct {
DegradedErr string `json:"degraded_error"` DegradedErr string `json:"degraded_error"`
} }
type ClusterNodeFiles struct {
LastUpdate int64 `json:"last_update"` // unix timestamp
Files map[string][]string `json:"files"`
}
type ClusterProcess struct { type ClusterProcess struct {
ID string `json:"id"` ID string `json:"id"`
Owner string `json:"owner"` Owner string `json:"owner"`

View File

@@ -3,6 +3,7 @@ package api
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"time"
"github.com/datarhei/core/v16/cluster" "github.com/datarhei/core/v16/cluster"
"github.com/datarhei/core/v16/cluster/proxy" "github.com/datarhei/core/v16/cluster/proxy"
@@ -57,17 +58,18 @@ func (h *ClusterHandler) About(c echo.Context) error {
about := api.ClusterAbout{ about := api.ClusterAbout{
ID: state.ID, ID: state.ID,
Name: state.Name,
Leader: state.Leader,
Address: state.Address, Address: state.Address,
ClusterAPIAddress: state.ClusterAPIAddress,
CoreAPIAddress: state.CoreAPIAddress,
Raft: api.ClusterRaft{ Raft: api.ClusterRaft{
Server: []api.ClusterRaftServer{}, Address: state.Raft.Address,
Stats: api.ClusterRaftStats{ State: state.Raft.State,
State: state.Raft.Stats.State, LastContact: state.Raft.LastContact.Seconds() * 1000,
LastContact: state.Raft.Stats.LastContact.Seconds() * 1000, NumPeers: state.Raft.NumPeers,
NumPeers: state.Raft.Stats.NumPeers, LogTerm: state.Raft.LogTerm,
}, LogIndex: state.Raft.LogIndex,
}, },
Nodes: []api.ClusterNode{},
Version: state.Version.String(), Version: state.Version.String(),
Degraded: state.Degraded, Degraded: state.Degraded,
} }
@@ -76,25 +78,53 @@ func (h *ClusterHandler) About(c echo.Context) error {
about.DegradedErr = state.DegradedErr.Error() about.DegradedErr = state.DegradedErr.Error()
} }
for _, n := range state.Raft.Server {
about.Raft.Server = append(about.Raft.Server, api.ClusterRaftServer{
ID: n.ID,
Address: n.Address,
Voter: n.Voter,
Leader: n.Leader,
})
}
for _, node := range state.Nodes { for _, node := range state.Nodes {
n := api.ClusterNode{} about.Nodes = append(about.Nodes, h.marshalClusterNode(node))
n.Marshal(node)
about.Nodes = append(about.Nodes, n)
} }
return c.JSON(http.StatusOK, about) return c.JSON(http.StatusOK, about)
} }
func (h *ClusterHandler) marshalClusterNode(node cluster.ClusterNode) api.ClusterNode {
n := api.ClusterNode{
ID: node.ID,
Name: node.Name,
Version: node.Version,
Status: node.Status,
Voter: node.Voter,
Leader: node.Leader,
Address: node.Address,
CreatedAt: node.CreatedAt.Format(time.RFC3339),
Uptime: int64(node.Uptime.Seconds()),
LastContact: node.LastContact.Seconds() * 1000,
Latency: node.Latency.Seconds() * 1000,
Core: api.ClusterNodeCore{
Address: node.Core.Address,
Status: node.Core.Status,
LastContact: node.Core.LastContact.Seconds() * 1000,
Latency: node.Core.Latency.Seconds() * 1000,
},
Resources: api.ClusterNodeResources{
IsThrottling: node.Resources.IsThrottling,
NCPU: node.Resources.NCPU,
CPU: node.Resources.CPU,
CPULimit: node.Resources.CPULimit,
Mem: node.Resources.Mem,
MemLimit: node.Resources.MemLimit,
},
}
if node.Error != nil {
n.Error = node.Error.Error()
}
if node.Core.Error != nil {
n.Core.Error = node.Core.Error.Error()
}
return n
}
// Healthy returns whether the cluster is healthy // Healthy returns whether the cluster is healthy
// @Summary Whether the cluster is healthy // @Summary Whether the cluster is healthy
// @Description Whether the cluster is healthy // @Description Whether the cluster is healthy

View File

@@ -24,16 +24,12 @@ import (
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /api/v3/cluster/node [get] // @Router /api/v3/cluster/node [get]
func (h *ClusterHandler) GetNodes(c echo.Context) error { func (h *ClusterHandler) GetNodes(c echo.Context) error {
nodes := h.proxy.ListNodes() about, _ := h.cluster.About()
list := []api.ClusterNode{} list := []api.ClusterNode{}
for _, node := range nodes { for _, node := range about.Nodes {
about := node.About() list = append(list, h.marshalClusterNode(node))
n := api.ClusterNode{}
n.Marshal(about)
list = append(list, n)
} }
return c.JSON(http.StatusOK, list) return c.JSON(http.StatusOK, list)
@@ -53,26 +49,17 @@ func (h *ClusterHandler) GetNodes(c echo.Context) error {
func (h *ClusterHandler) GetNode(c echo.Context) error { func (h *ClusterHandler) GetNode(c echo.Context) error {
id := util.PathParam(c, "id") id := util.PathParam(c, "id")
peer, err := h.proxy.GetNodeReader(id) about, _ := h.cluster.About()
if err != nil {
return api.Err(http.StatusNotFound, "", "node not found: %s", err.Error()) for _, node := range about.Nodes {
if node.ID != id {
continue
} }
about := peer.About() return c.JSON(http.StatusOK, h.marshalClusterNode(node))
node := api.ClusterNode{
ID: about.ID,
Name: about.Name,
Address: about.Address,
CreatedAt: about.CreatedAt.Format(time.RFC3339),
Uptime: int64(about.Uptime.Seconds()),
LastContact: about.LastContact.Unix(),
Latency: about.Latency.Seconds() * 1000,
State: about.State,
Resources: api.ClusterNodeResources(about.Resources),
} }
return c.JSON(http.StatusOK, node) return api.Err(http.StatusNotFound, "", "node not found")
} }
// GetNodeVersion returns the proxy node version with the given ID // GetNodeVersion returns the proxy node version with the given ID