Add node resource error, replace ping with about

This commit is contained in:
Ingo Oppermann
2023-07-25 17:17:20 +02:00
parent 81581091e8
commit d74165a90a
9 changed files with 182 additions and 60 deletions

View File

@@ -1317,6 +1317,7 @@ type ClusterNodeResources struct {
CPULimit float64 // Defined CPU load limit, 0-100*ncpu CPULimit float64 // Defined CPU load limit, 0-100*ncpu
Mem uint64 // Currently used memory in bytes Mem uint64 // Currently used memory in bytes
MemLimit uint64 // Defined memory limit in bytes MemLimit uint64 // Defined memory limit in bytes
Error error
} }
type ClusterNode struct { type ClusterNode struct {
@@ -1419,6 +1420,7 @@ func (c *cluster) About() (ClusterAbout, error) {
CPULimit: nodeAbout.Resources.CPULimit, CPULimit: nodeAbout.Resources.CPULimit,
Mem: nodeAbout.Resources.Mem, Mem: nodeAbout.Resources.Mem,
MemLimit: nodeAbout.Resources.MemLimit, MemLimit: nodeAbout.Resources.MemLimit,
Error: nodeAbout.Resources.Error,
}, },
} }

View File

@@ -37,12 +37,12 @@ type Node interface {
} }
type NodeReader interface { type NodeReader interface {
Ping() (time.Duration, error)
About() NodeAbout About() NodeAbout
Version() NodeVersion Version() NodeVersion
Resources() NodeResources Resources() NodeResources
Files() NodeFiles FileList(storage, pattern string) ([]clientapi.FileInfo, error)
ProxyFileList() NodeFiles
GetURL(prefix, path string) (*url.URL, error) GetURL(prefix, path string) (*url.URL, error)
GetFile(prefix, path string, offset int64) (io.ReadCloser, error) GetFile(prefix, path string, offset int64) (io.ReadCloser, error)
@@ -67,6 +67,7 @@ type NodeResources struct {
CPULimit float64 // Defined CPU load limit, 0-100*ncpu CPULimit float64 // Defined CPU load limit, 0-100*ncpu
Mem uint64 // Currently used memory in bytes Mem uint64 // Currently used memory in bytes
MemLimit uint64 // Defined memory limit in bytes MemLimit uint64 // Defined memory limit in bytes
Error error // Last error
} }
type NodeAbout struct { type NodeAbout struct {
@@ -112,6 +113,7 @@ type node struct {
peerErr error peerErr error
peerLock sync.RWMutex peerLock sync.RWMutex
peerWg sync.WaitGroup peerWg sync.WaitGroup
peerAbout clientapi.About
disconnect context.CancelFunc disconnect context.CancelFunc
lastContact time.Time lastContact time.Time
@@ -123,6 +125,7 @@ type node struct {
cpuLimit float64 cpuLimit float64
mem uint64 mem uint64
memLimit uint64 memLimit uint64
err error
} }
config *config.Config config *config.Config
@@ -154,6 +157,7 @@ func NewNode(id, address string, config *config.Config) Node {
n.resources.cpuLimit = 100 n.resources.cpuLimit = 100
n.resources.mem = 0 n.resources.mem = 0
n.resources.memLimit = 0 n.resources.memLimit = 0
n.resources.err = fmt.Errorf("not initialized")
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
n.disconnect = cancel n.disconnect = cancel
@@ -340,18 +344,18 @@ func (n *node) Disconnect() {
} }
func (n *node) pingPeer(ctx context.Context, wg *sync.WaitGroup) { func (n *node) pingPeer(ctx context.Context, wg *sync.WaitGroup) {
ticker := time.NewTicker(time.Second) ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop() defer ticker.Stop()
defer wg.Done() defer wg.Done()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
// Ping about, latency, err := n.AboutPeer()
latency, err := n.Ping()
n.peerLock.Lock() n.peerLock.Lock()
n.peerErr = err n.peerErr = err
n.peerAbout = about
n.peerLock.Unlock() n.peerLock.Unlock()
n.stateLock.Lock() n.stateLock.Lock()
@@ -391,10 +395,6 @@ func (n *node) updateResources(ctx context.Context, wg *sync.WaitGroup) {
}, },
}) })
n.peerLock.Lock()
n.peerErr = err
n.peerLock.Unlock()
if err != nil { if err != nil {
n.stateLock.Lock() n.stateLock.Lock()
n.resources.throttling = true n.resources.throttling = true
@@ -403,7 +403,7 @@ func (n *node) updateResources(ctx context.Context, wg *sync.WaitGroup) {
n.resources.cpuLimit = 100 n.resources.cpuLimit = 100
n.resources.mem = 0 n.resources.mem = 0
n.resources.memLimit = 0 n.resources.memLimit = 0
n.state = stateDisconnected n.resources.err = err
n.stateLock.Unlock() n.stateLock.Unlock()
continue continue
@@ -453,7 +453,7 @@ func (n *node) updateResources(ctx context.Context, wg *sync.WaitGroup) {
n.resources.mem = 0 n.resources.mem = 0
n.resources.memLimit = 0 n.resources.memLimit = 0
} }
n.lastContact = time.Now() n.resources.err = nil
n.stateLock.Unlock() n.stateLock.Unlock()
case <-ctx.Done(): case <-ctx.Done():
return return
@@ -461,19 +461,6 @@ func (n *node) updateResources(ctx context.Context, wg *sync.WaitGroup) {
} }
} }
func (n *node) Ping() (time.Duration, error) {
n.peerLock.RLock()
defer n.peerLock.RUnlock()
if n.peer == nil {
return 0, ErrNoPeer
}
latency, err := n.peer.Ping()
return latency, err
}
func (n *node) Metrics(query clientapi.MetricsQuery) (clientapi.MetricsResponse, error) { func (n *node) Metrics(query clientapi.MetricsQuery) (clientapi.MetricsResponse, error) {
n.peerLock.RLock() n.peerLock.RLock()
defer n.peerLock.RUnlock() defer n.peerLock.RUnlock()
@@ -485,41 +472,32 @@ func (n *node) Metrics(query clientapi.MetricsQuery) (clientapi.MetricsResponse,
return n.peer.Metrics(query) return n.peer.Metrics(query)
} }
func (n *node) AboutPeer() (clientapi.About, error) { func (n *node) AboutPeer() (clientapi.About, time.Duration, error) {
n.peerLock.RLock() n.peerLock.RLock()
defer n.peerLock.RUnlock() defer n.peerLock.RUnlock()
if n.peer == nil { if n.peer == nil {
return clientapi.About{}, ErrNoPeer return clientapi.About{}, 0, ErrNoPeer
} }
return n.peer.About(false) start := time.Now()
about, err := n.peer.About(false)
return about, time.Since(start), err
} }
func (n *node) About() NodeAbout { func (n *node) About() NodeAbout {
about, err := n.AboutPeer() n.peerLock.Lock()
createdAt, err := time.Parse(time.RFC3339, n.peerAbout.CreatedAt)
n.stateLock.RLock()
defer n.stateLock.RUnlock()
if err != nil {
return NodeAbout{
ID: n.id,
Address: n.address,
State: stateDisconnected.String(),
Error: err,
LastContact: n.lastContact,
Resources: NodeResources{
IsThrottling: true,
NCPU: 1,
},
}
}
createdAt, err := time.Parse(time.RFC3339, about.CreatedAt)
if err != nil { if err != nil {
createdAt = time.Now() createdAt = time.Now()
} }
name := n.peerAbout.Name
n.peerLock.Unlock()
n.stateLock.RLock()
defer n.stateLock.RUnlock()
state := n.state state := n.state
if time.Since(n.lastContact) > 3*time.Second { if time.Since(n.lastContact) > 3*time.Second {
@@ -528,7 +506,7 @@ func (n *node) About() NodeAbout {
nodeAbout := NodeAbout{ nodeAbout := NodeAbout{
ID: n.id, ID: n.id,
Name: about.Name, Name: name,
Address: n.address, Address: n.address,
State: state.String(), State: state.String(),
Error: n.peerErr, Error: n.peerErr,
@@ -543,6 +521,7 @@ func (n *node) About() NodeAbout {
CPULimit: n.resources.cpuLimit, CPULimit: n.resources.cpuLimit,
Mem: n.resources.mem, Mem: n.resources.mem,
MemLimit: n.resources.memLimit, MemLimit: n.resources.memLimit,
Error: n.resources.err,
}, },
} }
@@ -563,29 +542,27 @@ func (n *node) Resources() NodeResources {
} }
func (n *node) Version() NodeVersion { func (n *node) Version() NodeVersion {
about, err := n.AboutPeer() n.peerLock.Lock()
if err != nil { defer n.peerLock.Unlock()
return NodeVersion{}
}
build, err := time.Parse(time.RFC3339, about.Version.Build) build, err := time.Parse(time.RFC3339, n.peerAbout.Version.Build)
if err != nil { if err != nil {
build = time.Time{} build = time.Time{}
} }
version := NodeVersion{ version := NodeVersion{
Number: about.Version.Number, Number: n.peerAbout.Version.Number,
Commit: about.Version.Commit, Commit: n.peerAbout.Version.Commit,
Branch: about.Version.Branch, Branch: n.peerAbout.Version.Branch,
Build: build, Build: build,
Arch: about.Version.Arch, Arch: n.peerAbout.Version.Arch,
Compiler: about.Version.Compiler, Compiler: n.peerAbout.Version.Compiler,
} }
return version return version
} }
func (n *node) Files() NodeFiles { func (n *node) ProxyFileList() NodeFiles {
id := n.About().ID id := n.About().ID
files := NodeFiles{ files := NodeFiles{
@@ -732,6 +709,17 @@ func (n *node) files() []string {
return filesList return filesList
} }
func (n *node) FileList(storage, pattern string) ([]clientapi.FileInfo, error) {
n.peerLock.RLock()
defer n.peerLock.RUnlock()
if n.peer == nil {
return nil, ErrNoPeer
}
return n.peer.FilesystemList(storage, pattern, "", "")
}
func cloneURL(src *url.URL) *url.URL { func cloneURL(src *url.URL) *url.URL {
dst := &url.URL{ dst := &url.URL{
Scheme: src.Scheme, Scheme: src.Scheme,

View File

@@ -37,10 +37,13 @@ type ProxyReader interface {
FindNodeFromProcess(id app.ProcessID) (string, error) FindNodeFromProcess(id app.ProcessID) (string, error)
Resources() map[string]NodeResources Resources() map[string]NodeResources
ListProcesses(ProcessListOptions) []clientapi.Process ListProcesses(ProcessListOptions) []clientapi.Process
ListProxyProcesses() []Process ListProxyProcesses() []Process
ProbeProcess(nodeid string, id app.ProcessID) (clientapi.Probe, error) ProbeProcess(nodeid string, id app.ProcessID) (clientapi.Probe, error)
ListFiles(storage, patter string) []clientapi.FileInfo
GetURL(prefix, path string) (*url.URL, error) GetURL(prefix, path string) (*url.URL, error)
GetFile(prefix, path string, offset int64) (io.ReadCloser, error) GetFile(prefix, path string, offset int64) (io.ReadCloser, error)
GetFileInfo(prefix, path string) (int64, time.Time, error) GetFileInfo(prefix, path string) (int64, time.Time, error)
@@ -343,6 +346,49 @@ func (p *proxy) getNodeForFile(prefix, path string) (Node, error) {
return p.GetNode(id) return p.GetNode(id)
} }
func (p *proxy) ListFiles(storage, pattern string) []clientapi.FileInfo {
filesChan := make(chan []clientapi.FileInfo, 64)
filesList := []clientapi.FileInfo{}
wgList := sync.WaitGroup{}
wgList.Add(1)
go func() {
defer wgList.Done()
for list := range filesChan {
filesList = append(filesList, list...)
}
}()
wg := sync.WaitGroup{}
p.nodesLock.RLock()
for _, node := range p.nodes {
wg.Add(1)
go func(node Node, p chan<- []clientapi.FileInfo) {
defer wg.Done()
files, err := node.FileList(storage, pattern)
if err != nil {
return
}
p <- files
}(node, filesChan)
}
p.nodesLock.RUnlock()
wg.Wait()
close(filesChan)
wgList.Wait()
return filesList
}
type Process struct { type Process struct {
NodeID string NodeID string
Order string Order string

View File

@@ -36,6 +36,7 @@ type ClusterNodeResources struct {
CPULimit float64 `json:"cpu_limit"` // percent 0-100*npcu CPULimit float64 `json:"cpu_limit"` // percent 0-100*npcu
Mem uint64 `json:"memory_used_bytes"` // bytes Mem uint64 `json:"memory_used_bytes"` // bytes
MemLimit uint64 `json:"memory_limit_bytes"` // bytes MemLimit uint64 `json:"memory_limit_bytes"` // bytes
Error string `json:"error"`
} }
type ClusterRaft struct { type ClusterRaft struct {

View File

@@ -5,6 +5,7 @@ type FileInfo struct {
Name string `json:"name" jsonschema:"minLength=1"` Name string `json:"name" jsonschema:"minLength=1"`
Size int64 `json:"size_bytes" jsonschema:"minimum=0" format:"int64"` Size int64 `json:"size_bytes" jsonschema:"minimum=0" format:"int64"`
LastMod int64 `json:"last_modified" jsonschema:"minimum=0" format:"int64"` LastMod int64 `json:"last_modified" jsonschema:"minimum=0" format:"int64"`
CoreID string `json:"core_id,omitempty"`
} }
// FilesystemInfo represents information about a filesystem // FilesystemInfo represents information about a filesystem

View File

@@ -122,6 +122,10 @@ func (h *ClusterHandler) marshalClusterNode(node cluster.ClusterNode) api.Cluste
n.Core.Error = node.Core.Error.Error() n.Core.Error = node.Core.Error.Error()
} }
if node.Resources.Error != nil {
n.Resources.Error = node.Resources.Error.Error()
}
return n return n
} }

View File

@@ -0,0 +1,78 @@
package api
import (
"net/http"
"strconv"
"time"
"github.com/datarhei/core/v16/http/api"
"github.com/datarhei/core/v16/http/handler/util"
"github.com/datarhei/core/v16/io/fs"
"github.com/labstack/echo/v4"
)
// ListFiles lists all files on a filesystem
// @Summary List all files on a filesystem
// @Description List all files on a filesystem. The listing can be ordered by name, size, or date of last modification in ascending or descending order.
// @Tags v16.?.?
// @ID cluster-3-list-files
// @Produce json
// @Param storage path string true "Name of the filesystem"
// @Param glob query string false "glob pattern for file names"
// @Param size_min query int64 false "minimal size of files"
// @Param size_max query int64 false "maximal size of files"
// @Param lastmod_start query int64 false "minimal last modification time"
// @Param lastmod_end query int64 false "maximal last modification time"
// @Param sort query string false "none, name, size, lastmod"
// @Param order query string false "asc, desc"
// @Success 200 {array} api.FileInfo
// @Success 500 {object} api.Error
// @Security ApiKeyAuth
// @Router /api/v3/cluster/fs/{storage} [get]
func (h *ClusterHandler) ListFiles(c echo.Context) error {
//name := util.PathParam(c, "storage")
pattern := util.DefaultQuery(c, "glob", "")
sizeMin := util.DefaultQuery(c, "size_min", "0")
sizeMax := util.DefaultQuery(c, "size_max", "0")
modifiedStart := util.DefaultQuery(c, "lastmod_start", "")
modifiedEnd := util.DefaultQuery(c, "lastmod_end", "")
//sortby := util.DefaultQuery(c, "sort", "none")
//order := util.DefaultQuery(c, "order", "asc")
options := fs.ListOptions{
Pattern: pattern,
}
if x, err := strconv.ParseInt(sizeMin, 10, 64); err != nil {
return api.Err(http.StatusBadRequest, "", "size_min: %s", err.Error())
} else {
options.SizeMin = x
}
if x, err := strconv.ParseInt(sizeMax, 10, 64); err != nil {
return api.Err(http.StatusBadRequest, "", "size_max: %s", err.Error())
} else {
options.SizeMax = x
}
if len(modifiedStart) != 0 {
if x, err := strconv.ParseInt(modifiedStart, 10, 64); err != nil {
return api.Err(http.StatusBadRequest, "", "lastmod_start: %s", err.Error())
} else {
t := time.Unix(x, 0)
options.ModifiedStart = &t
}
}
if len(modifiedEnd) != 0 {
if x, err := strconv.ParseInt(modifiedEnd, 10, 64); err != nil {
return api.Err(http.StatusBadRequest, "", "lastmode_end: %s", err.Error())
} else {
t := time.Unix(x+1, 0)
options.ModifiedEnd = &t
}
}
return api.Err(http.StatusNotImplemented, "", "not implemented")
}

View File

@@ -118,7 +118,7 @@ func (h *ClusterHandler) GetNodeFiles(c echo.Context) error {
Files: make(map[string][]string), Files: make(map[string][]string),
} }
peerFiles := peer.Files() peerFiles := peer.ProxyFileList()
files.LastUpdate = peerFiles.LastUpdate.Unix() files.LastUpdate = peerFiles.LastUpdate.Unix()

View File

@@ -722,6 +722,8 @@ func (s *server) setRoutesV3(v3 *echo.Group) {
v3.GET("/cluster/node/:id/process", s.v3handler.cluster.ListNodeProcesses) v3.GET("/cluster/node/:id/process", s.v3handler.cluster.ListNodeProcesses)
v3.GET("/cluster/node/:id/version", s.v3handler.cluster.GetNodeVersion) v3.GET("/cluster/node/:id/version", s.v3handler.cluster.GetNodeVersion)
v3.GET("/cluster/fs/:storage", s.v3handler.cluster.ListFiles)
if !s.readOnly { if !s.readOnly {
v3.PUT("/cluster/transfer/:id", s.v3handler.cluster.TransferLeadership) v3.PUT("/cluster/transfer/:id", s.v3handler.cluster.TransferLeadership)
v3.PUT("/cluster/leave", s.v3handler.cluster.Leave) v3.PUT("/cluster/leave", s.v3handler.cluster.Leave)