mirror of
https://github.com/datarhei/core.git
synced 2025-10-04 23:53:12 +08:00
WIP: introducing cluster versioning, degraded mode
This commit is contained in:
@@ -95,6 +95,14 @@ func NewAPI(config APIConfig) (API, error) {
|
|||||||
doc := a.router.Group("/v1/swagger/*")
|
doc := a.router.Group("/v1/swagger/*")
|
||||||
doc.GET("", echoSwagger.EchoWrapHandler(echoSwagger.InstanceName("ClusterAPI")))
|
doc.GET("", echoSwagger.EchoWrapHandler(echoSwagger.InstanceName("ClusterAPI")))
|
||||||
|
|
||||||
|
a.router.GET("/", func(c echo.Context) error {
|
||||||
|
return c.JSON(http.StatusOK, Version.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
a.router.GET("/v1/about", func(c echo.Context) error {
|
||||||
|
return c.JSON(http.StatusOK, Version.String())
|
||||||
|
})
|
||||||
|
|
||||||
a.router.POST("/v1/server", a.AddServer)
|
a.router.POST("/v1/server", a.AddServer)
|
||||||
a.router.DELETE("/v1/server/:id", a.RemoveServer)
|
a.router.DELETE("/v1/server/:id", a.RemoveServer)
|
||||||
|
|
||||||
|
@@ -48,8 +48,23 @@ type APIClient struct {
|
|||||||
Client *http.Client
|
Client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) Version() (string, error) {
|
||||||
|
data, err := c.call(http.MethodGet, "/", "", nil, "")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var version string
|
||||||
|
err = json.Unmarshal(data, &version)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *APIClient) CoreAPIAddress() (string, error) {
|
func (c *APIClient) CoreAPIAddress() (string, error) {
|
||||||
data, err := c.call(http.MethodGet, "/core", "", nil, "")
|
data, err := c.call(http.MethodGet, "/v1/core", "", nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -69,13 +84,13 @@ func (c *APIClient) Join(origin string, r JoinRequest) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.call(http.MethodPost, "/server", "application/json", bytes.NewReader(data), origin)
|
_, err = c.call(http.MethodPost, "/v1/server", "application/json", bytes.NewReader(data), origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *APIClient) Leave(origin string, id string) error {
|
func (c *APIClient) Leave(origin string, id string) error {
|
||||||
_, err := c.call(http.MethodDelete, "/server/"+id, "application/json", nil, origin)
|
_, err := c.call(http.MethodDelete, "/v1/server/"+id, "application/json", nil, origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -86,13 +101,13 @@ func (c *APIClient) AddProcess(origin string, r AddProcessRequest) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.call(http.MethodPost, "/process", "application/json", bytes.NewReader(data), origin)
|
_, err = c.call(http.MethodPost, "/v1/process", "application/json", bytes.NewReader(data), origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *APIClient) RemoveProcess(origin string, id app.ProcessID) error {
|
func (c *APIClient) RemoveProcess(origin string, id app.ProcessID) error {
|
||||||
_, err := c.call(http.MethodDelete, "/process/"+id.ID+"?domain="+id.Domain, "application/json", nil, origin)
|
_, err := c.call(http.MethodDelete, "/v1/process/"+id.ID+"?domain="+id.Domain, "application/json", nil, origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -103,7 +118,7 @@ func (c *APIClient) UpdateProcess(origin string, id app.ProcessID, r UpdateProce
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.call(http.MethodPut, "/process/"+id.ID+"?domain="+id.Domain, "application/json", bytes.NewReader(data), origin)
|
_, err = c.call(http.MethodPut, "/v1/process/"+id.ID+"?domain="+id.Domain, "application/json", bytes.NewReader(data), origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -114,7 +129,7 @@ func (c *APIClient) SetProcessMetadata(origin string, id app.ProcessID, key stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.call(http.MethodPut, "/process/"+id.ID+"/metadata/"+key+"?domain="+id.Domain, "application/json", bytes.NewReader(data), origin)
|
_, err = c.call(http.MethodPut, "/v1/process/"+id.ID+"/metadata/"+key+"?domain="+id.Domain, "application/json", bytes.NewReader(data), origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -125,7 +140,7 @@ func (c *APIClient) AddIdentity(origin string, r AddIdentityRequest) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.call(http.MethodPost, "/iam/user", "application/json", bytes.NewReader(data), origin)
|
_, err = c.call(http.MethodPost, "/v1/iam/user", "application/json", bytes.NewReader(data), origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -136,7 +151,7 @@ func (c *APIClient) UpdateIdentity(origin, name string, r UpdateIdentityRequest)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.call(http.MethodPut, "/iam/user/"+name, "application/json", bytes.NewReader(data), origin)
|
_, err = c.call(http.MethodPut, "/v1/iam/user/"+name, "application/json", bytes.NewReader(data), origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -147,19 +162,19 @@ func (c *APIClient) SetPolicies(origin, name string, r SetPoliciesRequest) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.call(http.MethodPut, "/iam/user/"+name+"/policies", "application/json", bytes.NewReader(data), origin)
|
_, err = c.call(http.MethodPut, "/v1/iam/user/"+name+"/policies", "application/json", bytes.NewReader(data), origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *APIClient) RemoveIdentity(origin string, name string) error {
|
func (c *APIClient) RemoveIdentity(origin string, name string) error {
|
||||||
_, err := c.call(http.MethodDelete, "/iam/user/"+name, "application/json", nil, origin)
|
_, err := c.call(http.MethodDelete, "/v1/iam/user/"+name, "application/json", nil, origin)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *APIClient) Snapshot() (io.ReadCloser, error) {
|
func (c *APIClient) Snapshot() (io.ReadCloser, error) {
|
||||||
return c.stream(http.MethodGet, "/snapshot", "", nil, "")
|
return c.stream(http.MethodGet, "/v1/snapshot", "", nil, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *APIClient) stream(method, path, contentType string, data io.Reader, origin string) (io.ReadCloser, error) {
|
func (c *APIClient) stream(method, path, contentType string, data io.Reader, origin string) (io.ReadCloser, error) {
|
||||||
@@ -167,7 +182,7 @@ func (c *APIClient) stream(method, path, contentType string, data io.Reader, ori
|
|||||||
return nil, fmt.Errorf("no address defined")
|
return nil, fmt.Errorf("no address defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
address := "http://" + c.Address + "/v1" + path
|
address := "http://" + c.Address + path
|
||||||
|
|
||||||
req, err := http.NewRequest(method, address, data)
|
req, err := http.NewRequest(method, address, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -3,9 +3,11 @@ package cluster
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
gonet "net"
|
gonet "net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -124,6 +126,9 @@ type cluster struct {
|
|||||||
|
|
||||||
coreAddress string
|
coreAddress string
|
||||||
|
|
||||||
|
isDegraded bool
|
||||||
|
stateLock sync.Mutex
|
||||||
|
|
||||||
isRaftLeader bool
|
isRaftLeader bool
|
||||||
hasRaftLeader bool
|
hasRaftLeader bool
|
||||||
isLeader bool
|
isLeader bool
|
||||||
@@ -133,6 +138,8 @@ type cluster struct {
|
|||||||
nodesLock sync.RWMutex
|
nodesLock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrDegraded = errors.New("cluster is currently degraded")
|
||||||
|
|
||||||
func New(config ClusterConfig) (Cluster, error) {
|
func New(config ClusterConfig) (Cluster, error) {
|
||||||
c := &cluster{
|
c := &cluster{
|
||||||
id: config.ID,
|
id: config.ID,
|
||||||
@@ -301,7 +308,14 @@ func (c *cluster) ClusterAPIAddress(raftAddress string) (string, error) {
|
|||||||
raftAddress = c.Address()
|
raftAddress = c.Address()
|
||||||
}
|
}
|
||||||
|
|
||||||
host, port, _ := gonet.SplitHostPort(raftAddress)
|
return clusterAPIAddress(raftAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clusterAPIAddress(raftAddress string) (string, error) {
|
||||||
|
host, port, err := gonet.SplitHostPort(raftAddress)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
p, err := strconv.Atoi(port)
|
p, err := strconv.Atoi(port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -379,6 +393,13 @@ func (c *cluster) IsRaftLeader() bool {
|
|||||||
return c.isRaftLeader
|
return c.isRaftLeader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cluster) IsDegraded() bool {
|
||||||
|
c.stateLock.Lock()
|
||||||
|
defer c.stateLock.Unlock()
|
||||||
|
|
||||||
|
return c.isDegraded
|
||||||
|
}
|
||||||
|
|
||||||
func (c *cluster) Leave(origin, id string) error {
|
func (c *cluster) Leave(origin, id string) error {
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
id = c.id
|
id = c.id
|
||||||
@@ -524,19 +545,6 @@ func (c *cluster) Join(origin, id, raftAddress, peerAddress string) error {
|
|||||||
"address": raftAddress,
|
"address": raftAddress,
|
||||||
}).Log("Received join request for remote server")
|
}).Log("Received join request for remote server")
|
||||||
|
|
||||||
// connect to the peer's API in order to find out if our version is compatible
|
|
||||||
address, err := c.CoreAPIAddress(raftAddress)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("peer API doesn't respond: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
node := proxy.NewNode(address)
|
|
||||||
err = node.Connect()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't connect to peer: %w", err)
|
|
||||||
}
|
|
||||||
defer node.Disconnect()
|
|
||||||
|
|
||||||
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.Error().WithError(err).Log("Raft configuration")
|
||||||
@@ -620,30 +628,44 @@ func (c *cluster) trackNodeChanges() {
|
|||||||
|
|
||||||
_, ok := c.nodes[id]
|
_, ok := c.nodes[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
address, err := c.CoreAPIAddress(server.Address)
|
logger := c.logger.WithFields(log.Fields{
|
||||||
|
"id": server.ID,
|
||||||
|
"address": server.Address,
|
||||||
|
})
|
||||||
|
|
||||||
|
address, err := c.ClusterAPIAddress(server.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warn().WithError(err).WithFields(log.Fields{
|
logger.Warn().WithError(err).Log("Discovering cluster API address")
|
||||||
"id": id,
|
|
||||||
"address": server.Address,
|
|
||||||
}).Log("Discovering core API address")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
node := proxy.NewNode(address)
|
if !checkClusterVersion(address) {
|
||||||
|
logger.Warn().Log("Version mismatch. Cluster will end up in degraded mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := apiclient.APIClient{
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
|
||||||
|
coreAddress, err := client.CoreAPIAddress()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn().WithError(err).Log("Retrieve core API address")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
node := proxy.NewNode(coreAddress)
|
||||||
err = node.Connect()
|
err = node.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warn().WithError(err).WithFields(log.Fields{
|
c.logger.Warn().WithError(err).Log("Connecting to core API")
|
||||||
"id": id,
|
|
||||||
"address": server.Address,
|
|
||||||
}).Log("Connecting to core API")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Check constraints
|
||||||
|
|
||||||
if _, err := c.proxy.AddNode(id, node); err != nil {
|
if _, err := c.proxy.AddNode(id, node); err != nil {
|
||||||
c.logger.Warn().WithError(err).WithFields(log.Fields{
|
c.logger.Warn().WithError(err).Log("Adding node")
|
||||||
"id": id,
|
node.Disconnect()
|
||||||
"address": address,
|
continue
|
||||||
}).Log("Adding node")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.nodes[id] = node
|
c.nodes[id] = node
|
||||||
@@ -658,18 +680,94 @@ func (c *cluster) trackNodeChanges() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Disconnect()
|
|
||||||
c.proxy.RemoveNode(id)
|
c.proxy.RemoveNode(id)
|
||||||
|
node.Disconnect()
|
||||||
delete(c.nodes, id)
|
delete(c.nodes, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.nodesLock.Unlock()
|
c.nodesLock.Unlock()
|
||||||
|
|
||||||
|
// Put the cluster in "degraded" mode in case there's a version mismatch
|
||||||
|
isDegraded := !c.checkClusterVersions(servers)
|
||||||
|
|
||||||
|
c.stateLock.Lock()
|
||||||
|
c.isDegraded = isDegraded
|
||||||
|
c.stateLock.Unlock()
|
||||||
case <-c.shutdownCh:
|
case <-c.shutdownCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cluster) checkClusterVersions(servers []raft.Server) bool {
|
||||||
|
ok := true
|
||||||
|
okChan := make(chan bool, 64)
|
||||||
|
|
||||||
|
wgSummary := sync.WaitGroup{}
|
||||||
|
wgSummary.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wgSummary.Done()
|
||||||
|
|
||||||
|
for okServer := range okChan {
|
||||||
|
if !okServer {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
|
for _, server := range servers {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func(server raft.Server, p chan<- bool) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
address, err := clusterAPIAddress(server.Address)
|
||||||
|
if err != nil {
|
||||||
|
p <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p <- checkClusterVersion(address)
|
||||||
|
}(server, okChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
close(okChan)
|
||||||
|
|
||||||
|
wgSummary.Wait()
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkClusterVersion(address string) bool {
|
||||||
|
client := apiclient.APIClient{
|
||||||
|
Address: address,
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := client.Version()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := ParseClusterVersion(v)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Version.Equal(version) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// trackLeaderChanges registers an Observer with raft in order to receive updates
|
// trackLeaderChanges registers an Observer with raft in order to receive updates
|
||||||
// about leader changes, in order to keep the forwarder up to date.
|
// about leader changes, in order to keep the forwarder up to date.
|
||||||
func (c *cluster) trackLeaderChanges() {
|
func (c *cluster) trackLeaderChanges() {
|
||||||
@@ -705,6 +803,10 @@ func (c *cluster) GetProcess(id app.ProcessID) (store.Process, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) AddProcess(origin string, config *app.Config) error {
|
func (c *cluster) AddProcess(origin string, config *app.Config) error {
|
||||||
|
if c.IsDegraded() {
|
||||||
|
return ErrDegraded
|
||||||
|
}
|
||||||
|
|
||||||
if !c.IsRaftLeader() {
|
if !c.IsRaftLeader() {
|
||||||
return c.forwarder.AddProcess(origin, config)
|
return c.forwarder.AddProcess(origin, config)
|
||||||
}
|
}
|
||||||
@@ -720,6 +822,10 @@ func (c *cluster) AddProcess(origin string, config *app.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) RemoveProcess(origin string, id app.ProcessID) error {
|
func (c *cluster) RemoveProcess(origin string, id app.ProcessID) error {
|
||||||
|
if c.IsDegraded() {
|
||||||
|
return ErrDegraded
|
||||||
|
}
|
||||||
|
|
||||||
if !c.IsRaftLeader() {
|
if !c.IsRaftLeader() {
|
||||||
return c.forwarder.RemoveProcess(origin, id)
|
return c.forwarder.RemoveProcess(origin, id)
|
||||||
}
|
}
|
||||||
@@ -735,6 +841,10 @@ func (c *cluster) RemoveProcess(origin string, id app.ProcessID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) UpdateProcess(origin string, id app.ProcessID, config *app.Config) error {
|
func (c *cluster) UpdateProcess(origin string, id app.ProcessID, config *app.Config) error {
|
||||||
|
if c.IsDegraded() {
|
||||||
|
return ErrDegraded
|
||||||
|
}
|
||||||
|
|
||||||
if !c.IsRaftLeader() {
|
if !c.IsRaftLeader() {
|
||||||
return c.forwarder.UpdateProcess(origin, id, config)
|
return c.forwarder.UpdateProcess(origin, id, config)
|
||||||
}
|
}
|
||||||
@@ -751,6 +861,10 @@ func (c *cluster) UpdateProcess(origin string, id app.ProcessID, config *app.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) SetProcessMetadata(origin string, id app.ProcessID, key string, data interface{}) error {
|
func (c *cluster) SetProcessMetadata(origin string, id app.ProcessID, key string, data interface{}) error {
|
||||||
|
if c.IsDegraded() {
|
||||||
|
return ErrDegraded
|
||||||
|
}
|
||||||
|
|
||||||
if !c.IsRaftLeader() {
|
if !c.IsRaftLeader() {
|
||||||
return c.forwarder.SetProcessMetadata(origin, id, key, data)
|
return c.forwarder.SetProcessMetadata(origin, id, key, data)
|
||||||
}
|
}
|
||||||
@@ -784,7 +898,7 @@ func (c *cluster) IAM(superuser iamidentity.User, jwtRealm, jwtSecret string) (i
|
|||||||
Superuser: superuser,
|
Superuser: superuser,
|
||||||
JWTRealm: jwtRealm,
|
JWTRealm: jwtRealm,
|
||||||
JWTSecret: jwtSecret,
|
JWTSecret: jwtSecret,
|
||||||
Logger: c.logger.WithField("logname", "iam"),
|
Logger: c.logger.WithComponent("IAM"),
|
||||||
}, c.store)
|
}, c.store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cluster iam: %w", err)
|
return nil, fmt.Errorf("cluster iam: %w", err)
|
||||||
@@ -822,6 +936,10 @@ func (c *cluster) ListUserPolicies(name string) (time.Time, []iamaccess.Policy)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) AddIdentity(origin string, identity iamidentity.User) error {
|
func (c *cluster) AddIdentity(origin string, identity iamidentity.User) error {
|
||||||
|
if c.IsDegraded() {
|
||||||
|
return ErrDegraded
|
||||||
|
}
|
||||||
|
|
||||||
if !c.IsRaftLeader() {
|
if !c.IsRaftLeader() {
|
||||||
return c.forwarder.AddIdentity(origin, identity)
|
return c.forwarder.AddIdentity(origin, identity)
|
||||||
}
|
}
|
||||||
@@ -837,6 +955,10 @@ func (c *cluster) AddIdentity(origin string, identity iamidentity.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) UpdateIdentity(origin, name string, identity iamidentity.User) error {
|
func (c *cluster) UpdateIdentity(origin, name string, identity iamidentity.User) error {
|
||||||
|
if c.IsDegraded() {
|
||||||
|
return ErrDegraded
|
||||||
|
}
|
||||||
|
|
||||||
if !c.IsRaftLeader() {
|
if !c.IsRaftLeader() {
|
||||||
return c.forwarder.UpdateIdentity(origin, name, identity)
|
return c.forwarder.UpdateIdentity(origin, name, identity)
|
||||||
}
|
}
|
||||||
@@ -853,6 +975,10 @@ func (c *cluster) UpdateIdentity(origin, name string, identity iamidentity.User)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) SetPolicies(origin, name string, policies []iamaccess.Policy) error {
|
func (c *cluster) SetPolicies(origin, name string, policies []iamaccess.Policy) error {
|
||||||
|
if c.IsDegraded() {
|
||||||
|
return ErrDegraded
|
||||||
|
}
|
||||||
|
|
||||||
if !c.IsRaftLeader() {
|
if !c.IsRaftLeader() {
|
||||||
return c.forwarder.SetPolicies(origin, name, policies)
|
return c.forwarder.SetPolicies(origin, name, policies)
|
||||||
}
|
}
|
||||||
@@ -869,6 +995,10 @@ func (c *cluster) SetPolicies(origin, name string, policies []iamaccess.Policy)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) RemoveIdentity(origin string, name string) error {
|
func (c *cluster) RemoveIdentity(origin string, name string) error {
|
||||||
|
if c.IsDegraded() {
|
||||||
|
return ErrDegraded
|
||||||
|
}
|
||||||
|
|
||||||
if !c.IsRaftLeader() {
|
if !c.IsRaftLeader() {
|
||||||
return c.forwarder.RemoveIdentity(origin, name)
|
return c.forwarder.RemoveIdentity(origin, name)
|
||||||
}
|
}
|
||||||
@@ -897,32 +1027,42 @@ func (c *cluster) applyCommand(cmd *store.Command) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClusterServer struct {
|
type ClusterRaftServer struct {
|
||||||
ID string
|
ID string
|
||||||
Address string
|
Address string
|
||||||
Voter bool
|
Voter bool
|
||||||
Leader bool
|
Leader bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClusterStats struct {
|
type ClusterRaftStats struct {
|
||||||
State string
|
State string
|
||||||
LastContact time.Duration
|
LastContact time.Duration
|
||||||
NumPeers uint64
|
NumPeers uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClusterRaft struct {
|
||||||
|
Server []ClusterRaftServer
|
||||||
|
Stats ClusterRaftStats
|
||||||
|
}
|
||||||
|
|
||||||
type ClusterAbout struct {
|
type ClusterAbout struct {
|
||||||
ID string
|
ID string
|
||||||
Address string
|
Address string
|
||||||
ClusterAPIAddress string
|
ClusterAPIAddress string
|
||||||
CoreAPIAddress string
|
CoreAPIAddress string
|
||||||
Nodes []ClusterServer
|
Raft ClusterRaft
|
||||||
Stats ClusterStats
|
Nodes []proxy.NodeAbout
|
||||||
|
Version ClusterVersion
|
||||||
|
Degraded bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) About() (ClusterAbout, error) {
|
func (c *cluster) About() (ClusterAbout, error) {
|
||||||
|
degraded := c.IsDegraded()
|
||||||
|
|
||||||
about := ClusterAbout{
|
about := ClusterAbout{
|
||||||
ID: c.id,
|
ID: c.id,
|
||||||
Address: c.Address(),
|
Address: c.Address(),
|
||||||
|
Degraded: degraded,
|
||||||
}
|
}
|
||||||
|
|
||||||
if address, err := c.ClusterAPIAddress(""); err == nil {
|
if address, err := c.ClusterAPIAddress(""); err == nil {
|
||||||
@@ -935,9 +1075,9 @@ func (c *cluster) About() (ClusterAbout, error) {
|
|||||||
|
|
||||||
stats := c.raft.Stats()
|
stats := c.raft.Stats()
|
||||||
|
|
||||||
about.Stats.State = stats.State
|
about.Raft.Stats.State = stats.State
|
||||||
about.Stats.LastContact = stats.LastContact
|
about.Raft.Stats.LastContact = stats.LastContact
|
||||||
about.Stats.NumPeers = stats.NumPeers
|
about.Raft.Stats.NumPeers = stats.NumPeers
|
||||||
|
|
||||||
servers, err := c.raft.Servers()
|
servers, err := c.raft.Servers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -946,14 +1086,21 @@ func (c *cluster) About() (ClusterAbout, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, server := range servers {
|
for _, server := range servers {
|
||||||
node := ClusterServer{
|
node := ClusterRaftServer{
|
||||||
ID: server.ID,
|
ID: server.ID,
|
||||||
Address: server.Address,
|
Address: server.Address,
|
||||||
Voter: server.Voter,
|
Voter: server.Voter,
|
||||||
Leader: server.Leader,
|
Leader: server.Leader,
|
||||||
}
|
}
|
||||||
|
|
||||||
about.Nodes = append(about.Nodes, node)
|
about.Raft.Server = append(about.Raft.Server, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
about.Version = Version
|
||||||
|
|
||||||
|
nodes := c.ProxyReader().ListNodes()
|
||||||
|
for _, node := range nodes {
|
||||||
|
about.Nodes = append(about.Nodes, node.About())
|
||||||
}
|
}
|
||||||
|
|
||||||
return about, nil
|
return about, nil
|
||||||
|
@@ -333,6 +333,10 @@ func (c *cluster) startSynchronizeAndRebalance(ctx context.Context, interval tim
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
if c.IsDegraded() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
c.doSynchronize(emergency)
|
c.doSynchronize(emergency)
|
||||||
|
|
||||||
if !emergency {
|
if !emergency {
|
||||||
@@ -551,7 +555,7 @@ func (c *cluster) doRebalance(emergency bool) {
|
|||||||
|
|
||||||
c.logger.Debug().WithFields(log.Fields{
|
c.logger.Debug().WithFields(log.Fields{
|
||||||
"have": have,
|
"have": have,
|
||||||
"nodes": nodes,
|
"nodes": nodesMap,
|
||||||
}).Log("Rebalance")
|
}).Log("Rebalance")
|
||||||
|
|
||||||
opStack, _ := rebalance(have, nodesMap)
|
opStack, _ := rebalance(have, nodesMap)
|
||||||
|
116
cluster/node.go
Normal file
116
cluster/node.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/cluster/client"
|
||||||
|
"github.com/datarhei/core/v16/cluster/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type clusterNode struct {
|
||||||
|
client client.APIClient
|
||||||
|
|
||||||
|
version string
|
||||||
|
lastContact time.Time
|
||||||
|
latency time.Duration
|
||||||
|
pingLock sync.RWMutex
|
||||||
|
|
||||||
|
runLock sync.Mutex
|
||||||
|
cancelPing context.CancelFunc
|
||||||
|
|
||||||
|
proxyNode proxy.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClusterNode(address string) *clusterNode {
|
||||||
|
n := &clusterNode{
|
||||||
|
version: "0.0.0",
|
||||||
|
client: client.APIClient{
|
||||||
|
Address: address,
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *clusterNode) Start() error {
|
||||||
|
n.runLock.Lock()
|
||||||
|
defer n.runLock.Unlock()
|
||||||
|
|
||||||
|
if n.cancelPing != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := n.client.Version()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n.version = version
|
||||||
|
|
||||||
|
address, err := n.CoreAPIAddress()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n.proxyNode = proxy.NewNode(address)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
n.cancelPing = cancel
|
||||||
|
|
||||||
|
go n.ping(ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *clusterNode) Stop() error {
|
||||||
|
n.runLock.Lock()
|
||||||
|
defer n.runLock.Unlock()
|
||||||
|
|
||||||
|
if n.cancelPing == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n.cancelPing()
|
||||||
|
n.cancelPing = nil
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *clusterNode) Version() string {
|
||||||
|
n.pingLock.RLock()
|
||||||
|
defer n.pingLock.RUnlock()
|
||||||
|
|
||||||
|
return n.version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *clusterNode) CoreAPIAddress() (string, error) {
|
||||||
|
return n.client.CoreAPIAddress()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *clusterNode) ping(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
start := time.Now()
|
||||||
|
version, err := n.client.Version()
|
||||||
|
if err == nil {
|
||||||
|
n.pingLock.Lock()
|
||||||
|
n.version = version
|
||||||
|
n.lastContact = time.Now()
|
||||||
|
n.latency = time.Since(start)
|
||||||
|
n.pingLock.Unlock()
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -167,8 +167,7 @@ func (n *node) Connect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
peer, err := client.New(client.Config{
|
peer, err := client.New(client.Config{
|
||||||
Address: n.address,
|
Address: n.address,
|
||||||
Auth0Token: "",
|
|
||||||
Client: &http.Client{
|
Client: &http.Client{
|
||||||
Timeout: 5 * time.Second,
|
Timeout: 5 * time.Second,
|
||||||
},
|
},
|
||||||
@@ -566,25 +565,20 @@ func (n *node) files() {
|
|||||||
filesList := []string{}
|
filesList := []string{}
|
||||||
errorList := []error{}
|
errorList := []error{}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
wgList := sync.WaitGroup{}
|
wgList := sync.WaitGroup{}
|
||||||
wgList.Add(1)
|
wgList.Add(1)
|
||||||
|
|
||||||
go func(ctx context.Context) {
|
go func() {
|
||||||
defer wgList.Done()
|
defer wgList.Done()
|
||||||
|
|
||||||
for {
|
for file := range filesChan {
|
||||||
select {
|
filesList = append(filesList, file)
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case file := <-filesChan:
|
|
||||||
filesList = append(filesList, file)
|
|
||||||
case err := <-errorsChan:
|
|
||||||
errorList = append(errorList, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}(ctx)
|
|
||||||
|
for err := range errorsChan {
|
||||||
|
errorList = append(errorList, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
@@ -687,7 +681,8 @@ func (n *node) files() {
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
cancel()
|
close(filesChan)
|
||||||
|
close(errorsChan)
|
||||||
|
|
||||||
wgList.Wait()
|
wgList.Wait()
|
||||||
|
|
||||||
|
@@ -107,13 +107,8 @@ type CommandSetProcessNodeMap struct {
|
|||||||
Map map[string]string
|
Map map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement a FSM
|
type storeData struct {
|
||||||
type store struct {
|
Version uint64
|
||||||
lock sync.RWMutex
|
|
||||||
callback func(op Operation)
|
|
||||||
|
|
||||||
logger log.Logger
|
|
||||||
|
|
||||||
Process map[string]Process
|
Process map[string]Process
|
||||||
ProcessNodeMap map[string]string
|
ProcessNodeMap map[string]string
|
||||||
|
|
||||||
@@ -128,24 +123,43 @@ type store struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *storeData) init() {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
s.Version = 1
|
||||||
|
s.Process = map[string]Process{}
|
||||||
|
s.ProcessNodeMap = map[string]string{}
|
||||||
|
s.Users.UpdatedAt = now
|
||||||
|
s.Users.Users = map[string]identity.User{}
|
||||||
|
s.Policies.UpdatedAt = now
|
||||||
|
s.Policies.Policies = map[string][]access.Policy{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store implements a raft.FSM
|
||||||
|
type store struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
callback func(op Operation)
|
||||||
|
|
||||||
|
logger log.Logger
|
||||||
|
|
||||||
|
data storeData
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Logger log.Logger
|
Logger log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStore(config Config) (Store, error) {
|
func NewStore(config Config) (Store, error) {
|
||||||
s := &store{
|
s := &store{
|
||||||
Process: map[string]Process{},
|
logger: config.Logger,
|
||||||
ProcessNodeMap: map[string]string{},
|
|
||||||
logger: config.Logger,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Users.Users = map[string]identity.User{}
|
|
||||||
s.Policies.Policies = map[string][]access.Policy{}
|
|
||||||
|
|
||||||
if s.logger == nil {
|
if s.logger == nil {
|
||||||
s.logger = log.New("")
|
s.logger = log.New("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.data.init()
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,13 +328,13 @@ func (s *store) addProcess(cmd CommandAddProcess) error {
|
|||||||
return fmt.Errorf("the process with the ID '%s' must have limits defined", id)
|
return fmt.Errorf("the process with the ID '%s' must have limits defined", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := s.Process[id]
|
_, ok := s.data.Process[id]
|
||||||
if ok {
|
if ok {
|
||||||
return fmt.Errorf("the process with the ID '%s' already exists", id)
|
return fmt.Errorf("the process with the ID '%s' already exists", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
s.Process[id] = Process{
|
s.data.Process[id] = Process{
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
Config: cmd.Config,
|
Config: cmd.Config,
|
||||||
@@ -336,12 +350,12 @@ func (s *store) removeProcess(cmd CommandRemoveProcess) error {
|
|||||||
|
|
||||||
id := cmd.ID.String()
|
id := cmd.ID.String()
|
||||||
|
|
||||||
_, ok := s.Process[id]
|
_, ok := s.data.Process[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("the process with the ID '%s' doesn't exist", id)
|
return fmt.Errorf("the process with the ID '%s' doesn't exist", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(s.Process, id)
|
delete(s.data.Process, id)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -357,7 +371,7 @@ func (s *store) updateProcess(cmd CommandUpdateProcess) error {
|
|||||||
return fmt.Errorf("the process with the ID '%s' must have limits defined", dstid)
|
return fmt.Errorf("the process with the ID '%s' must have limits defined", dstid)
|
||||||
}
|
}
|
||||||
|
|
||||||
p, ok := s.Process[srcid]
|
p, ok := s.data.Process[srcid]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("the process with the ID '%s' doesn't exists", srcid)
|
return fmt.Errorf("the process with the ID '%s' doesn't exists", srcid)
|
||||||
}
|
}
|
||||||
@@ -367,7 +381,7 @@ func (s *store) updateProcess(cmd CommandUpdateProcess) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if srcid == dstid {
|
if srcid == dstid {
|
||||||
s.Process[srcid] = Process{
|
s.data.Process[srcid] = Process{
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
Config: cmd.Config,
|
Config: cmd.Config,
|
||||||
}
|
}
|
||||||
@@ -375,13 +389,13 @@ func (s *store) updateProcess(cmd CommandUpdateProcess) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok = s.Process[dstid]
|
_, ok = s.data.Process[dstid]
|
||||||
if ok {
|
if ok {
|
||||||
return fmt.Errorf("the process with the ID '%s' already exists", dstid)
|
return fmt.Errorf("the process with the ID '%s' already exists", dstid)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(s.Process, srcid)
|
delete(s.data.Process, srcid)
|
||||||
s.Process[dstid] = Process{
|
s.data.Process[dstid] = Process{
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
Config: cmd.Config,
|
Config: cmd.Config,
|
||||||
}
|
}
|
||||||
@@ -395,7 +409,7 @@ func (s *store) setProcessMetadata(cmd CommandSetProcessMetadata) error {
|
|||||||
|
|
||||||
id := cmd.ID.String()
|
id := cmd.ID.String()
|
||||||
|
|
||||||
p, ok := s.Process[id]
|
p, ok := s.data.Process[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("the process with the ID '%s' doesn't exists", cmd.ID)
|
return fmt.Errorf("the process with the ID '%s' doesn't exists", cmd.ID)
|
||||||
}
|
}
|
||||||
@@ -411,7 +425,7 @@ func (s *store) setProcessMetadata(cmd CommandSetProcessMetadata) error {
|
|||||||
}
|
}
|
||||||
p.UpdatedAt = time.Now()
|
p.UpdatedAt = time.Now()
|
||||||
|
|
||||||
s.Process[id] = p
|
s.data.Process[id] = p
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -420,13 +434,13 @@ func (s *store) addIdentity(cmd CommandAddIdentity) error {
|
|||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
_, ok := s.Users.Users[cmd.Identity.Name]
|
_, ok := s.data.Users.Users[cmd.Identity.Name]
|
||||||
if ok {
|
if ok {
|
||||||
return fmt.Errorf("the identity with the name '%s' already exists", cmd.Identity.Name)
|
return fmt.Errorf("the identity with the name '%s' already exists", cmd.Identity.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Users.UpdatedAt = time.Now()
|
s.data.Users.UpdatedAt = time.Now()
|
||||||
s.Users.Users[cmd.Identity.Name] = cmd.Identity
|
s.data.Users.Users[cmd.Identity.Name] = cmd.Identity
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -435,32 +449,32 @@ func (s *store) updateIdentity(cmd CommandUpdateIdentity) error {
|
|||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
_, ok := s.Users.Users[cmd.Name]
|
_, ok := s.data.Users.Users[cmd.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("the identity with the name '%s' doesn't exist", cmd.Name)
|
return fmt.Errorf("the identity with the name '%s' doesn't exist", cmd.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Name == cmd.Identity.Name {
|
if cmd.Name == cmd.Identity.Name {
|
||||||
s.Users.UpdatedAt = time.Now()
|
s.data.Users.UpdatedAt = time.Now()
|
||||||
s.Users.Users[cmd.Identity.Name] = cmd.Identity
|
s.data.Users.Users[cmd.Identity.Name] = cmd.Identity
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok = s.Users.Users[cmd.Identity.Name]
|
_, ok = s.data.Users.Users[cmd.Identity.Name]
|
||||||
if ok {
|
if ok {
|
||||||
return fmt.Errorf("the identity with the name '%s' already exists", cmd.Identity.Name)
|
return fmt.Errorf("the identity with the name '%s' already exists", cmd.Identity.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
s.Users.UpdatedAt = now
|
s.data.Users.UpdatedAt = now
|
||||||
s.Users.Users[cmd.Identity.Name] = cmd.Identity
|
s.data.Users.Users[cmd.Identity.Name] = cmd.Identity
|
||||||
s.Policies.UpdatedAt = now
|
s.data.Policies.UpdatedAt = now
|
||||||
s.Policies.Policies[cmd.Identity.Name] = s.Policies.Policies[cmd.Name]
|
s.data.Policies.Policies[cmd.Identity.Name] = s.data.Policies.Policies[cmd.Name]
|
||||||
|
|
||||||
delete(s.Users.Users, cmd.Name)
|
delete(s.data.Users.Users, cmd.Name)
|
||||||
delete(s.Policies.Policies, cmd.Name)
|
delete(s.data.Policies.Policies, cmd.Name)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -469,10 +483,10 @@ func (s *store) removeIdentity(cmd CommandRemoveIdentity) error {
|
|||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
delete(s.Users.Users, cmd.Name)
|
delete(s.data.Users.Users, cmd.Name)
|
||||||
s.Users.UpdatedAt = time.Now()
|
s.data.Users.UpdatedAt = time.Now()
|
||||||
delete(s.Policies.Policies, cmd.Name)
|
delete(s.data.Policies.Policies, cmd.Name)
|
||||||
s.Policies.UpdatedAt = time.Now()
|
s.data.Policies.UpdatedAt = time.Now()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -481,13 +495,13 @@ func (s *store) setPolicies(cmd CommandSetPolicies) error {
|
|||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
if _, ok := s.Users.Users[cmd.Name]; !ok {
|
if _, ok := s.data.Users.Users[cmd.Name]; !ok {
|
||||||
return fmt.Errorf("the identity with the name '%s' doesn't exist", cmd.Name)
|
return fmt.Errorf("the identity with the name '%s' doesn't exist", cmd.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(s.Policies.Policies, cmd.Name)
|
delete(s.data.Policies.Policies, cmd.Name)
|
||||||
s.Policies.Policies[cmd.Name] = cmd.Policies
|
s.data.Policies.Policies[cmd.Name] = cmd.Policies
|
||||||
s.Policies.UpdatedAt = time.Now()
|
s.data.Policies.UpdatedAt = time.Now()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -496,7 +510,7 @@ func (s *store) setProcessNodeMap(cmd CommandSetProcessNodeMap) error {
|
|||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
s.ProcessNodeMap = cmd.Map
|
s.data.ProcessNodeMap = cmd.Map
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -514,7 +528,7 @@ func (s *store) Snapshot() (raft.FSMSnapshot, error) {
|
|||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
data, err := json.Marshal(s)
|
data, err := json.Marshal(&s.data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -532,20 +546,29 @@ func (s *store) Restore(snapshot io.ReadCloser) error {
|
|||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
data := storeData{}
|
||||||
|
data.init()
|
||||||
|
|
||||||
dec := json.NewDecoder(snapshot)
|
dec := json.NewDecoder(snapshot)
|
||||||
if err := dec.Decode(s); err != nil {
|
if err := dec.Decode(&data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for id, p := range s.Process {
|
for id, p := range data.Process {
|
||||||
if p.Metadata != nil {
|
if p.Metadata != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Metadata = map[string]interface{}{}
|
p.Metadata = map[string]interface{}{}
|
||||||
s.Process[id] = p
|
data.Process[id] = p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.Version == 0 {
|
||||||
|
data.Version = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
s.data = data
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +578,7 @@ func (s *store) ListProcesses() []Process {
|
|||||||
|
|
||||||
processes := []Process{}
|
processes := []Process{}
|
||||||
|
|
||||||
for _, p := range s.Process {
|
for _, p := range s.data.Process {
|
||||||
processes = append(processes, Process{
|
processes = append(processes, Process{
|
||||||
CreatedAt: p.CreatedAt,
|
CreatedAt: p.CreatedAt,
|
||||||
UpdatedAt: p.UpdatedAt,
|
UpdatedAt: p.UpdatedAt,
|
||||||
@@ -571,7 +594,7 @@ func (s *store) GetProcess(id app.ProcessID) (Process, error) {
|
|||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
process, ok := s.Process[id.String()]
|
process, ok := s.data.Process[id.String()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return Process{}, fmt.Errorf("not found")
|
return Process{}, fmt.Errorf("not found")
|
||||||
}
|
}
|
||||||
@@ -589,10 +612,10 @@ func (s *store) ListUsers() Users {
|
|||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
u := Users{
|
u := Users{
|
||||||
UpdatedAt: s.Users.UpdatedAt,
|
UpdatedAt: s.data.Users.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range s.Users.Users {
|
for _, user := range s.data.Users.Users {
|
||||||
u.Users = append(u.Users, user)
|
u.Users = append(u.Users, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,10 +627,10 @@ func (s *store) GetUser(name string) Users {
|
|||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
u := Users{
|
u := Users{
|
||||||
UpdatedAt: s.Users.UpdatedAt,
|
UpdatedAt: s.data.Users.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
if user, ok := s.Users.Users[name]; ok {
|
if user, ok := s.data.Users.Users[name]; ok {
|
||||||
u.Users = append(u.Users, user)
|
u.Users = append(u.Users, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,10 +642,10 @@ func (s *store) ListPolicies() Policies {
|
|||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
p := Policies{
|
p := Policies{
|
||||||
UpdatedAt: s.Policies.UpdatedAt,
|
UpdatedAt: s.data.Policies.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, policies := range s.Policies.Policies {
|
for _, policies := range s.data.Policies.Policies {
|
||||||
p.Policies = append(p.Policies, policies...)
|
p.Policies = append(p.Policies, policies...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,10 +657,10 @@ func (s *store) ListUserPolicies(name string) Policies {
|
|||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
p := Policies{
|
p := Policies{
|
||||||
UpdatedAt: s.Policies.UpdatedAt,
|
UpdatedAt: s.data.Policies.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Policies = append(p.Policies, s.Policies.Policies[name]...)
|
p.Policies = append(p.Policies, s.data.Policies.Policies[name]...)
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
@@ -648,7 +671,7 @@ func (s *store) GetProcessNodeMap() map[string]string {
|
|||||||
|
|
||||||
m := map[string]string{}
|
m := map[string]string{}
|
||||||
|
|
||||||
for key, value := range s.ProcessNodeMap {
|
for key, value := range s.data.ProcessNodeMap {
|
||||||
m[key] = value
|
m[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,10 +29,10 @@ func TestCreateStore(t *testing.T) {
|
|||||||
s, err := createStore()
|
s, err := createStore()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NotNil(t, s.Process)
|
require.NotNil(t, s.data.Process)
|
||||||
require.NotNil(t, s.ProcessNodeMap)
|
require.NotNil(t, s.data.ProcessNodeMap)
|
||||||
require.NotNil(t, s.Users.Users)
|
require.NotNil(t, s.data.Users.Users)
|
||||||
require.NotNil(t, s.Policies.Policies)
|
require.NotNil(t, s.data.Policies.Policies)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddProcessCommand(t *testing.T) {
|
func TestAddProcessCommand(t *testing.T) {
|
||||||
@@ -52,9 +52,9 @@ func TestAddProcessCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, s.Process)
|
require.NotEmpty(t, s.data.Process)
|
||||||
|
|
||||||
p, ok := s.Process["foobar@"]
|
p, ok := s.data.Process["foobar@"]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
require.NotZero(t, p.CreatedAt)
|
require.NotZero(t, p.CreatedAt)
|
||||||
@@ -76,7 +76,7 @@ func TestAddProcess(t *testing.T) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Empty(t, s.Process)
|
require.Empty(t, s.data.Process)
|
||||||
|
|
||||||
config = &app.Config{
|
config = &app.Config{
|
||||||
ID: "foobar",
|
ID: "foobar",
|
||||||
@@ -88,7 +88,7 @@ func TestAddProcess(t *testing.T) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Process))
|
require.Equal(t, 1, len(s.data.Process))
|
||||||
|
|
||||||
config = &app.Config{
|
config = &app.Config{
|
||||||
ID: "foobar",
|
ID: "foobar",
|
||||||
@@ -100,7 +100,7 @@ func TestAddProcess(t *testing.T) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, 1, len(s.Process))
|
require.Equal(t, 1, len(s.data.Process))
|
||||||
|
|
||||||
config = &app.Config{
|
config = &app.Config{
|
||||||
ID: "foobar",
|
ID: "foobar",
|
||||||
@@ -113,7 +113,7 @@ func TestAddProcess(t *testing.T) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Process))
|
require.Equal(t, 2, len(s.data.Process))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveProcessCommand(t *testing.T) {
|
func TestRemoveProcessCommand(t *testing.T) {
|
||||||
@@ -133,7 +133,7 @@ func TestRemoveProcessCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, s.Process)
|
require.NotEmpty(t, s.data.Process)
|
||||||
|
|
||||||
err = s.applyCommand(Command{
|
err = s.applyCommand(Command{
|
||||||
Operation: OpRemoveProcess,
|
Operation: OpRemoveProcess,
|
||||||
@@ -142,7 +142,7 @@ func TestRemoveProcessCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, s.Process)
|
require.Empty(t, s.data.Process)
|
||||||
|
|
||||||
err = s.applyCommand(Command{
|
err = s.applyCommand(Command{
|
||||||
Operation: OpRemoveProcess,
|
Operation: OpRemoveProcess,
|
||||||
@@ -184,25 +184,25 @@ func TestRemoveProcess(t *testing.T) {
|
|||||||
Config: config1,
|
Config: config1,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Process))
|
require.Equal(t, 1, len(s.data.Process))
|
||||||
|
|
||||||
err = s.addProcess(CommandAddProcess{
|
err = s.addProcess(CommandAddProcess{
|
||||||
Config: config2,
|
Config: config2,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Process))
|
require.Equal(t, 2, len(s.data.Process))
|
||||||
|
|
||||||
err = s.removeProcess(CommandRemoveProcess{
|
err = s.removeProcess(CommandRemoveProcess{
|
||||||
ID: config1.ProcessID(),
|
ID: config1.ProcessID(),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Process))
|
require.Equal(t, 1, len(s.data.Process))
|
||||||
|
|
||||||
err = s.removeProcess(CommandRemoveProcess{
|
err = s.removeProcess(CommandRemoveProcess{
|
||||||
ID: config2.ProcessID(),
|
ID: config2.ProcessID(),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, s.Process)
|
require.Empty(t, s.data.Process)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateProcessCommand(t *testing.T) {
|
func TestUpdateProcessCommand(t *testing.T) {
|
||||||
@@ -224,7 +224,7 @@ func TestUpdateProcessCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, s.Process)
|
require.NotEmpty(t, s.data.Process)
|
||||||
|
|
||||||
config = &app.Config{
|
config = &app.Config{
|
||||||
ID: "foobaz",
|
ID: "foobaz",
|
||||||
@@ -240,7 +240,7 @@ func TestUpdateProcessCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, s.Process)
|
require.NotEmpty(t, s.data.Process)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateProcess(t *testing.T) {
|
func TestUpdateProcess(t *testing.T) {
|
||||||
@@ -263,13 +263,13 @@ func TestUpdateProcess(t *testing.T) {
|
|||||||
Config: config1,
|
Config: config1,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Process))
|
require.Equal(t, 1, len(s.data.Process))
|
||||||
|
|
||||||
err = s.addProcess(CommandAddProcess{
|
err = s.addProcess(CommandAddProcess{
|
||||||
Config: config2,
|
Config: config2,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Process))
|
require.Equal(t, 2, len(s.data.Process))
|
||||||
|
|
||||||
config := &app.Config{
|
config := &app.Config{
|
||||||
ID: "foobaz",
|
ID: "foobaz",
|
||||||
@@ -307,14 +307,14 @@ func TestUpdateProcess(t *testing.T) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Process))
|
require.Equal(t, 2, len(s.data.Process))
|
||||||
|
|
||||||
err = s.updateProcess(CommandUpdateProcess{
|
err = s.updateProcess(CommandUpdateProcess{
|
||||||
ID: config.ProcessID(),
|
ID: config.ProcessID(),
|
||||||
Config: config,
|
Config: config,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Process))
|
require.Equal(t, 2, len(s.data.Process))
|
||||||
|
|
||||||
config3 := &app.Config{
|
config3 := &app.Config{
|
||||||
ID: config.ID,
|
ID: config.ID,
|
||||||
@@ -327,7 +327,7 @@ func TestUpdateProcess(t *testing.T) {
|
|||||||
Config: config3,
|
Config: config3,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Process))
|
require.Equal(t, 2, len(s.data.Process))
|
||||||
|
|
||||||
_, err = s.GetProcess(config1.ProcessID())
|
_, err = s.GetProcess(config1.ProcessID())
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -356,7 +356,7 @@ func TestSetProcessMetadataCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, s.Process)
|
require.NotEmpty(t, s.data.Process)
|
||||||
|
|
||||||
p, err := s.GetProcess(config.ProcessID())
|
p, err := s.GetProcess(config.ProcessID())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -403,7 +403,7 @@ func TestSetProcessMetadata(t *testing.T) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Process))
|
require.Equal(t, 1, len(s.data.Process))
|
||||||
|
|
||||||
err = s.setProcessMetadata(CommandSetProcessMetadata{
|
err = s.setProcessMetadata(CommandSetProcessMetadata{
|
||||||
ID: config.ProcessID(),
|
ID: config.ProcessID(),
|
||||||
@@ -471,7 +471,7 @@ func TestAddIdentityCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddIdentity(t *testing.T) {
|
func TestAddIdentity(t *testing.T) {
|
||||||
@@ -486,15 +486,15 @@ func TestAddIdentity(t *testing.T) {
|
|||||||
Identity: identity,
|
Identity: identity,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
require.Equal(t, 0, len(s.Policies.Policies))
|
require.Equal(t, 0, len(s.data.Policies.Policies))
|
||||||
|
|
||||||
err = s.addIdentity(CommandAddIdentity{
|
err = s.addIdentity(CommandAddIdentity{
|
||||||
Identity: identity,
|
Identity: identity,
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
require.Equal(t, 0, len(s.Policies.Policies))
|
require.Equal(t, 0, len(s.data.Policies.Policies))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveIdentityCommand(t *testing.T) {
|
func TestRemoveIdentityCommand(t *testing.T) {
|
||||||
@@ -512,7 +512,7 @@ func TestRemoveIdentityCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
|
|
||||||
err = s.applyCommand(Command{
|
err = s.applyCommand(Command{
|
||||||
Operation: OpRemoveIdentity,
|
Operation: OpRemoveIdentity,
|
||||||
@@ -521,7 +521,7 @@ func TestRemoveIdentityCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 0, len(s.Users.Users))
|
require.Equal(t, 0, len(s.data.Users.Users))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveIdentity(t *testing.T) {
|
func TestRemoveIdentity(t *testing.T) {
|
||||||
@@ -536,22 +536,22 @@ func TestRemoveIdentity(t *testing.T) {
|
|||||||
Identity: identity,
|
Identity: identity,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
require.Equal(t, 0, len(s.Policies.Policies))
|
require.Equal(t, 0, len(s.data.Policies.Policies))
|
||||||
|
|
||||||
err = s.removeIdentity(CommandRemoveIdentity{
|
err = s.removeIdentity(CommandRemoveIdentity{
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 0, len(s.Users.Users))
|
require.Equal(t, 0, len(s.data.Users.Users))
|
||||||
require.Equal(t, 0, len(s.Policies.Policies))
|
require.Equal(t, 0, len(s.data.Policies.Policies))
|
||||||
|
|
||||||
err = s.removeIdentity(CommandRemoveIdentity{
|
err = s.removeIdentity(CommandRemoveIdentity{
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 0, len(s.Users.Users))
|
require.Equal(t, 0, len(s.data.Users.Users))
|
||||||
require.Equal(t, 0, len(s.Policies.Policies))
|
require.Equal(t, 0, len(s.data.Policies.Policies))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetPoliciesCommand(t *testing.T) {
|
func TestSetPoliciesCommand(t *testing.T) {
|
||||||
@@ -569,8 +569,8 @@ func TestSetPoliciesCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
require.Equal(t, 0, len(s.Policies.Policies))
|
require.Equal(t, 0, len(s.data.Policies.Policies))
|
||||||
|
|
||||||
err = s.applyCommand(Command{
|
err = s.applyCommand(Command{
|
||||||
Operation: OpSetPolicies,
|
Operation: OpSetPolicies,
|
||||||
@@ -593,8 +593,8 @@ func TestSetPoliciesCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
require.Equal(t, 2, len(s.Policies.Policies["foobar"]))
|
require.Equal(t, 2, len(s.data.Policies.Policies["foobar"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetPolicies(t *testing.T) {
|
func TestSetPolicies(t *testing.T) {
|
||||||
@@ -625,22 +625,22 @@ func TestSetPolicies(t *testing.T) {
|
|||||||
Policies: policies,
|
Policies: policies,
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, 0, len(s.Policies.Policies["foobar"]))
|
require.Equal(t, 0, len(s.data.Policies.Policies["foobar"]))
|
||||||
|
|
||||||
err = s.addIdentity(CommandAddIdentity{
|
err = s.addIdentity(CommandAddIdentity{
|
||||||
Identity: identity,
|
Identity: identity,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
require.Equal(t, 0, len(s.Policies.Policies))
|
require.Equal(t, 0, len(s.data.Policies.Policies))
|
||||||
|
|
||||||
err = s.setPolicies(CommandSetPolicies{
|
err = s.setPolicies(CommandSetPolicies{
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Policies: policies,
|
Policies: policies,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
require.Equal(t, 2, len(s.Policies.Policies["foobar"]))
|
require.Equal(t, 2, len(s.data.Policies.Policies["foobar"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateUserCommand(t *testing.T) {
|
func TestUpdateUserCommand(t *testing.T) {
|
||||||
@@ -662,7 +662,7 @@ func TestUpdateUserCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
|
|
||||||
err = s.applyCommand(Command{
|
err = s.applyCommand(Command{
|
||||||
Operation: OpAddIdentity,
|
Operation: OpAddIdentity,
|
||||||
@@ -671,7 +671,7 @@ func TestUpdateUserCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Users.Users))
|
require.Equal(t, 2, len(s.data.Users.Users))
|
||||||
|
|
||||||
err = s.applyCommand(Command{
|
err = s.applyCommand(Command{
|
||||||
Operation: OpUpdateIdentity,
|
Operation: OpUpdateIdentity,
|
||||||
@@ -683,7 +683,7 @@ func TestUpdateUserCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Users.Users))
|
require.Equal(t, 2, len(s.data.Users.Users))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateIdentity(t *testing.T) {
|
func TestUpdateIdentity(t *testing.T) {
|
||||||
@@ -702,13 +702,13 @@ func TestUpdateIdentity(t *testing.T) {
|
|||||||
Identity: idty1,
|
Identity: idty1,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
|
|
||||||
err = s.addIdentity(CommandAddIdentity{
|
err = s.addIdentity(CommandAddIdentity{
|
||||||
Identity: idty2,
|
Identity: idty2,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Users.Users))
|
require.Equal(t, 2, len(s.data.Users.Users))
|
||||||
|
|
||||||
idty := identity.User{
|
idty := identity.User{
|
||||||
Name: "foobaz",
|
Name: "foobaz",
|
||||||
@@ -719,7 +719,7 @@ func TestUpdateIdentity(t *testing.T) {
|
|||||||
Identity: idty,
|
Identity: idty,
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, 2, len(s.Users.Users))
|
require.Equal(t, 2, len(s.data.Users.Users))
|
||||||
|
|
||||||
idty.Name = "foobar2"
|
idty.Name = "foobar2"
|
||||||
|
|
||||||
@@ -728,7 +728,7 @@ func TestUpdateIdentity(t *testing.T) {
|
|||||||
Identity: idty,
|
Identity: idty,
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, 2, len(s.Users.Users))
|
require.Equal(t, 2, len(s.data.Users.Users))
|
||||||
|
|
||||||
idty.Name = "foobaz"
|
idty.Name = "foobaz"
|
||||||
|
|
||||||
@@ -737,7 +737,7 @@ func TestUpdateIdentity(t *testing.T) {
|
|||||||
Identity: idty,
|
Identity: idty,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Users.Users))
|
require.Equal(t, 2, len(s.data.Users.Users))
|
||||||
|
|
||||||
u := s.GetUser("foobar1")
|
u := s.GetUser("foobar1")
|
||||||
require.Empty(t, u.Users)
|
require.Empty(t, u.Users)
|
||||||
@@ -776,22 +776,22 @@ func TestUpdateIdentityWithPolicies(t *testing.T) {
|
|||||||
Identity: idty1,
|
Identity: idty1,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
|
|
||||||
err = s.setPolicies(CommandSetPolicies{
|
err = s.setPolicies(CommandSetPolicies{
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Policies: policies,
|
Policies: policies,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(s.Policies.Policies["foobar"]))
|
require.Equal(t, 2, len(s.data.Policies.Policies["foobar"]))
|
||||||
|
|
||||||
err = s.updateIdentity(CommandUpdateIdentity{
|
err = s.updateIdentity(CommandUpdateIdentity{
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Identity: idty1,
|
Identity: idty1,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
require.Equal(t, 2, len(s.Policies.Policies["foobar"]))
|
require.Equal(t, 2, len(s.data.Policies.Policies["foobar"]))
|
||||||
|
|
||||||
idty2 := identity.User{
|
idty2 := identity.User{
|
||||||
Name: "foobaz",
|
Name: "foobaz",
|
||||||
@@ -802,9 +802,9 @@ func TestUpdateIdentityWithPolicies(t *testing.T) {
|
|||||||
Identity: idty2,
|
Identity: idty2,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(s.Users.Users))
|
require.Equal(t, 1, len(s.data.Users.Users))
|
||||||
require.Equal(t, 0, len(s.Policies.Policies["foobar"]))
|
require.Equal(t, 0, len(s.data.Policies.Policies["foobar"]))
|
||||||
require.Equal(t, 2, len(s.Policies.Policies["foobaz"]))
|
require.Equal(t, 2, len(s.data.Policies.Policies["foobaz"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetProcessNodeMapCommand(t *testing.T) {
|
func TestSetProcessNodeMapCommand(t *testing.T) {
|
||||||
@@ -822,7 +822,7 @@ func TestSetProcessNodeMapCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, m1, s.ProcessNodeMap)
|
require.Equal(t, m1, s.data.ProcessNodeMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetProcessNodeMap(t *testing.T) {
|
func TestSetProcessNodeMap(t *testing.T) {
|
||||||
@@ -837,7 +837,7 @@ func TestSetProcessNodeMap(t *testing.T) {
|
|||||||
Map: m1,
|
Map: m1,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, m1, s.ProcessNodeMap)
|
require.Equal(t, m1, s.data.ProcessNodeMap)
|
||||||
|
|
||||||
m2 := map[string]string{
|
m2 := map[string]string{
|
||||||
"key": "value2",
|
"key": "value2",
|
||||||
@@ -847,7 +847,7 @@ func TestSetProcessNodeMap(t *testing.T) {
|
|||||||
Map: m2,
|
Map: m2,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, m2, s.ProcessNodeMap)
|
require.Equal(t, m2, s.data.ProcessNodeMap)
|
||||||
|
|
||||||
m := s.GetProcessNodeMap()
|
m := s.GetProcessNodeMap()
|
||||||
require.Equal(t, m2, m)
|
require.Equal(t, m2, m)
|
||||||
|
43
cluster/version.go
Normal file
43
cluster/version.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClusterVersion struct {
|
||||||
|
Major uint64
|
||||||
|
Minor uint64
|
||||||
|
Patch uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ClusterVersion) String() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ClusterVersion) Equal(x ClusterVersion) bool {
|
||||||
|
return v.String() == x.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseClusterVersion(version string) (ClusterVersion, error) {
|
||||||
|
v := ClusterVersion{}
|
||||||
|
|
||||||
|
sv, err := semver.NewVersion(version)
|
||||||
|
if err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Major = sv.Major()
|
||||||
|
v.Minor = sv.Minor()
|
||||||
|
v.Patch = sv.Patch()
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version of the cluster
|
||||||
|
var Version = ClusterVersion{
|
||||||
|
Major: 1,
|
||||||
|
Minor: 0,
|
||||||
|
Patch: 0,
|
||||||
|
}
|
76
docs/docs.go
76
docs/docs.go
@@ -3782,17 +3782,23 @@ const docTemplate = `{
|
|||||||
"core_api_address": {
|
"core_api_address": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"degraded": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"server": {
|
"nodes": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/api.ClusterServer"
|
"$ref": "#/definitions/api.ClusterNode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stats": {
|
"raft": {
|
||||||
"$ref": "#/definitions/api.ClusterStats"
|
"$ref": "#/definitions/api.ClusterRaft"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3827,6 +3833,9 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"uptime_seconds": {
|
"uptime_seconds": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3863,9 +3872,11 @@ const docTemplate = `{
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"memory_limit_bytes": {
|
"memory_limit_bytes": {
|
||||||
|
"description": "bytes",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"memory_used_bytes": {
|
"memory_used_bytes": {
|
||||||
|
"description": "bytes",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"ncpu": {
|
"ncpu": {
|
||||||
@@ -3877,6 +3888,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"cpu": {
|
"cpu": {
|
||||||
|
"description": "percent 0-100*ncpu",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
@@ -3886,6 +3898,7 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"memory_bytes": {
|
"memory_bytes": {
|
||||||
|
"description": "bytes",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"node_id": {
|
"node_id": {
|
||||||
@@ -3901,6 +3914,7 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"runtime_seconds": {
|
"runtime_seconds": {
|
||||||
|
"description": "seconds",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
@@ -3908,7 +3922,21 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.ClusterServer": {
|
"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": {
|
||||||
@@ -3926,7 +3954,7 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.ClusterStats": {
|
"api.ClusterRaftStats": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"last_contact_ms": {
|
"last_contact_ms": {
|
||||||
@@ -4053,6 +4081,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"address": {
|
"address": {
|
||||||
|
"description": "ip:port",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"bootstrap": {
|
"bootstrap": {
|
||||||
@@ -4061,9 +4090,19 @@ const docTemplate = `{
|
|||||||
"debug": {
|
"debug": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"emergency_leader_timeout": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
"enable": {
|
"enable": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"node_recover_timeout": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
"peers": {
|
"peers": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -4072,6 +4111,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"recover": {
|
"recover": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sync_interval": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4261,9 +4305,11 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"max_cpu_usage": {
|
"max_cpu_usage": {
|
||||||
|
"description": "percent 0-100",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"max_memory_usage": {
|
"max_memory_usage": {
|
||||||
|
"description": "percent 0-100",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6225,6 +6271,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"address": {
|
"address": {
|
||||||
|
"description": "ip:port",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"bootstrap": {
|
"bootstrap": {
|
||||||
@@ -6233,9 +6280,19 @@ const docTemplate = `{
|
|||||||
"debug": {
|
"debug": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"emergency_leader_timeout": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
"enable": {
|
"enable": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"node_recover_timeout": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
"peers": {
|
"peers": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -6244,6 +6301,11 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"recover": {
|
"recover": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sync_interval": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6433,9 +6495,11 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"max_cpu_usage": {
|
"max_cpu_usage": {
|
||||||
|
"description": "percent 0-100",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"max_memory_usage": {
|
"max_memory_usage": {
|
||||||
|
"description": "percent 0-100",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3774,17 +3774,23 @@
|
|||||||
"core_api_address": {
|
"core_api_address": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"degraded": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"server": {
|
"nodes": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/api.ClusterServer"
|
"$ref": "#/definitions/api.ClusterNode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stats": {
|
"raft": {
|
||||||
"$ref": "#/definitions/api.ClusterStats"
|
"$ref": "#/definitions/api.ClusterRaft"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3819,6 +3825,9 @@
|
|||||||
},
|
},
|
||||||
"uptime_seconds": {
|
"uptime_seconds": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3855,9 +3864,11 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"memory_limit_bytes": {
|
"memory_limit_bytes": {
|
||||||
|
"description": "bytes",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"memory_used_bytes": {
|
"memory_used_bytes": {
|
||||||
|
"description": "bytes",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"ncpu": {
|
"ncpu": {
|
||||||
@@ -3869,6 +3880,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"cpu": {
|
"cpu": {
|
||||||
|
"description": "percent 0-100*ncpu",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
@@ -3878,6 +3890,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"memory_bytes": {
|
"memory_bytes": {
|
||||||
|
"description": "bytes",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"node_id": {
|
"node_id": {
|
||||||
@@ -3893,6 +3906,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"runtime_seconds": {
|
"runtime_seconds": {
|
||||||
|
"description": "seconds",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
@@ -3900,7 +3914,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.ClusterServer": {
|
"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": {
|
||||||
@@ -3918,7 +3946,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.ClusterStats": {
|
"api.ClusterRaftStats": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"last_contact_ms": {
|
"last_contact_ms": {
|
||||||
@@ -4045,6 +4073,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"address": {
|
"address": {
|
||||||
|
"description": "ip:port",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"bootstrap": {
|
"bootstrap": {
|
||||||
@@ -4053,9 +4082,19 @@
|
|||||||
"debug": {
|
"debug": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"emergency_leader_timeout": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
"enable": {
|
"enable": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"node_recover_timeout": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
"peers": {
|
"peers": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -4064,6 +4103,11 @@
|
|||||||
},
|
},
|
||||||
"recover": {
|
"recover": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sync_interval": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4253,9 +4297,11 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"max_cpu_usage": {
|
"max_cpu_usage": {
|
||||||
|
"description": "percent 0-100",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"max_memory_usage": {
|
"max_memory_usage": {
|
||||||
|
"description": "percent 0-100",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6217,6 +6263,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"address": {
|
"address": {
|
||||||
|
"description": "ip:port",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"bootstrap": {
|
"bootstrap": {
|
||||||
@@ -6225,9 +6272,19 @@
|
|||||||
"debug": {
|
"debug": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"emergency_leader_timeout": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
"enable": {
|
"enable": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"node_recover_timeout": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
"peers": {
|
"peers": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -6236,6 +6293,11 @@
|
|||||||
},
|
},
|
||||||
"recover": {
|
"recover": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sync_interval": {
|
||||||
|
"description": "seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6425,9 +6487,11 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"max_cpu_usage": {
|
"max_cpu_usage": {
|
||||||
|
"description": "percent 0-100",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"max_memory_usage": {
|
"max_memory_usage": {
|
||||||
|
"description": "percent 0-100",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -73,14 +73,18 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
core_api_address:
|
core_api_address:
|
||||||
type: string
|
type: string
|
||||||
|
degraded:
|
||||||
|
type: boolean
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
server:
|
nodes:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/api.ClusterServer'
|
$ref: '#/definitions/api.ClusterNode'
|
||||||
type: array
|
type: array
|
||||||
stats:
|
raft:
|
||||||
$ref: '#/definitions/api.ClusterStats'
|
$ref: '#/definitions/api.ClusterRaft'
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
api.ClusterNode:
|
api.ClusterNode:
|
||||||
properties:
|
properties:
|
||||||
@@ -104,6 +108,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
uptime_seconds:
|
uptime_seconds:
|
||||||
type: integer
|
type: integer
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
api.ClusterNodeFiles:
|
api.ClusterNodeFiles:
|
||||||
properties:
|
properties:
|
||||||
@@ -128,8 +134,10 @@ definitions:
|
|||||||
is_throttling:
|
is_throttling:
|
||||||
type: boolean
|
type: boolean
|
||||||
memory_limit_bytes:
|
memory_limit_bytes:
|
||||||
|
description: bytes
|
||||||
type: integer
|
type: integer
|
||||||
memory_used_bytes:
|
memory_used_bytes:
|
||||||
|
description: bytes
|
||||||
type: integer
|
type: integer
|
||||||
ncpu:
|
ncpu:
|
||||||
type: number
|
type: number
|
||||||
@@ -137,12 +145,14 @@ definitions:
|
|||||||
api.ClusterProcess:
|
api.ClusterProcess:
|
||||||
properties:
|
properties:
|
||||||
cpu:
|
cpu:
|
||||||
|
description: percent 0-100*ncpu
|
||||||
type: number
|
type: number
|
||||||
domain:
|
domain:
|
||||||
type: string
|
type: string
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
memory_bytes:
|
memory_bytes:
|
||||||
|
description: bytes
|
||||||
type: integer
|
type: integer
|
||||||
node_id:
|
node_id:
|
||||||
type: string
|
type: string
|
||||||
@@ -153,11 +163,21 @@ definitions:
|
|||||||
reference:
|
reference:
|
||||||
type: string
|
type: string
|
||||||
runtime_seconds:
|
runtime_seconds:
|
||||||
|
description: seconds
|
||||||
type: integer
|
type: integer
|
||||||
state:
|
state:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
api.ClusterServer:
|
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
|
description: raft address
|
||||||
@@ -169,7 +189,7 @@ definitions:
|
|||||||
voter:
|
voter:
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
api.ClusterStats:
|
api.ClusterRaftStats:
|
||||||
properties:
|
properties:
|
||||||
last_contact_ms:
|
last_contact_ms:
|
||||||
type: number
|
type: number
|
||||||
@@ -252,19 +272,32 @@ definitions:
|
|||||||
cluster:
|
cluster:
|
||||||
properties:
|
properties:
|
||||||
address:
|
address:
|
||||||
|
description: ip:port
|
||||||
type: string
|
type: string
|
||||||
bootstrap:
|
bootstrap:
|
||||||
type: boolean
|
type: boolean
|
||||||
debug:
|
debug:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
emergency_leader_timeout:
|
||||||
|
description: seconds
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
enable:
|
enable:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
node_recover_timeout:
|
||||||
|
description: seconds
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
peers:
|
peers:
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
recover:
|
recover:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
sync_interval:
|
||||||
|
description: seconds
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
created_at:
|
created_at:
|
||||||
description: When this config has been persisted
|
description: When this config has been persisted
|
||||||
@@ -393,8 +426,10 @@ definitions:
|
|||||||
resources:
|
resources:
|
||||||
properties:
|
properties:
|
||||||
max_cpu_usage:
|
max_cpu_usage:
|
||||||
|
description: percent 0-100
|
||||||
type: number
|
type: number
|
||||||
max_memory_usage:
|
max_memory_usage:
|
||||||
|
description: percent 0-100
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
router:
|
router:
|
||||||
@@ -1783,19 +1818,32 @@ definitions:
|
|||||||
cluster:
|
cluster:
|
||||||
properties:
|
properties:
|
||||||
address:
|
address:
|
||||||
|
description: ip:port
|
||||||
type: string
|
type: string
|
||||||
bootstrap:
|
bootstrap:
|
||||||
type: boolean
|
type: boolean
|
||||||
debug:
|
debug:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
emergency_leader_timeout:
|
||||||
|
description: seconds
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
enable:
|
enable:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
node_recover_timeout:
|
||||||
|
description: seconds
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
peers:
|
peers:
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
recover:
|
recover:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
sync_interval:
|
||||||
|
description: seconds
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
created_at:
|
created_at:
|
||||||
description: When this config has been persisted
|
description: When this config has been persisted
|
||||||
@@ -1924,8 +1972,10 @@ definitions:
|
|||||||
resources:
|
resources:
|
||||||
properties:
|
properties:
|
||||||
max_cpu_usage:
|
max_cpu_usage:
|
||||||
|
description: percent 0-100
|
||||||
type: number
|
type: number
|
||||||
max_memory_usage:
|
max_memory_usage:
|
||||||
|
description: percent 0-100
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
router:
|
router:
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import "encoding/json"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/cluster/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
type ClusterNode struct {
|
type ClusterNode struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
@@ -12,15 +17,28 @@ type ClusterNode struct {
|
|||||||
Latency float64 `json:"latency_ms"` // milliseconds
|
Latency float64 `json:"latency_ms"` // milliseconds
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
Resources ClusterNodeResources `json:"resources"`
|
Resources ClusterNodeResources `json:"resources"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ClusterNode) Marshal(about proxy.NodeAbout) {
|
||||||
|
n.ID = about.ID
|
||||||
|
n.Name = about.Name
|
||||||
|
n.Address = about.Address
|
||||||
|
n.CreatedAt = about.CreatedAt.Format(time.RFC3339)
|
||||||
|
n.Uptime = int64(about.Uptime.Seconds())
|
||||||
|
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 {
|
||||||
IsThrottling bool `json:"is_throttling"`
|
IsThrottling bool `json:"is_throttling"`
|
||||||
NCPU float64 `json:"ncpu"`
|
NCPU float64 `json:"ncpu"`
|
||||||
CPU float64 `json:"cpu_used"` // percent 0-100*npcu
|
CPU float64 `json:"cpu_used"` // percent 0-100*npcu
|
||||||
CPULimit float64 `json:"cpu_limit"` // percent 0-100*npcu
|
CPULimit float64 `json:"cpu_limit"` // percent 0-100*npcu
|
||||||
Mem uint64 `json:"memory_used_bytes"`
|
Mem uint64 `json:"memory_used_bytes"` // bytes
|
||||||
MemLimit uint64 `json:"memory_limit_bytes"`
|
MemLimit uint64 `json:"memory_limit_bytes"` // bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClusterNodeFiles struct {
|
type ClusterNodeFiles struct {
|
||||||
@@ -28,13 +46,35 @@ type ClusterNodeFiles struct {
|
|||||||
Files map[string][]string `json:"files"`
|
Files map[string][]string `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClusterServer struct {
|
type ClusterRaftServer struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Address string `json:"address"` // raft address
|
Address string `json:"address"` // raft address
|
||||||
Voter bool `json:"voter"`
|
Voter bool `json:"voter"`
|
||||||
Leader bool `json:"leader"`
|
Leader bool `json:"leader"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClusterRaftStats struct {
|
||||||
|
State string `json:"state"`
|
||||||
|
LastContact float64 `json:"last_contact_ms"`
|
||||||
|
NumPeers uint64 `json:"num_peers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterRaft struct {
|
||||||
|
Server []ClusterRaftServer `json:"server"`
|
||||||
|
Stats ClusterRaftStats `json:"stats"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterAbout struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
ClusterAPIAddress string `json:"cluster_api_address"`
|
||||||
|
CoreAPIAddress string `json:"core_api_address"`
|
||||||
|
Raft ClusterRaft `json:"raft"`
|
||||||
|
Nodes []ClusterNode `json:"nodes"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Degraded bool `json:"degraded"`
|
||||||
|
}
|
||||||
|
|
||||||
type ClusterProcess struct {
|
type ClusterProcess struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
@@ -43,22 +83,7 @@ type ClusterProcess struct {
|
|||||||
Reference string `json:"reference"`
|
Reference string `json:"reference"`
|
||||||
Order string `json:"order"`
|
Order string `json:"order"`
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
CPU json.Number `json:"cpu" swaggertype:"number" jsonschema:"type=number"`
|
CPU json.Number `json:"cpu" swaggertype:"number" jsonschema:"type=number"` // percent 0-100*ncpu
|
||||||
Memory uint64 `json:"memory_bytes"`
|
Memory uint64 `json:"memory_bytes"` // bytes
|
||||||
Runtime int64 `json:"runtime_seconds"`
|
Runtime int64 `json:"runtime_seconds"` // seconds
|
||||||
}
|
|
||||||
|
|
||||||
type ClusterStats struct {
|
|
||||||
State string `json:"state"`
|
|
||||||
LastContact float64 `json:"last_contact_ms"`
|
|
||||||
NumPeers uint64 `json:"num_peers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClusterAbout struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
ClusterAPIAddress string `json:"cluster_api_address"`
|
|
||||||
CoreAPIAddress string `json:"core_api_address"`
|
|
||||||
Server []ClusterServer `json:"server"`
|
|
||||||
Stats ClusterStats `json:"stats"`
|
|
||||||
}
|
}
|
||||||
|
@@ -65,16 +65,20 @@ func (h *ClusterHandler) About(c echo.Context) error {
|
|||||||
Address: state.Address,
|
Address: state.Address,
|
||||||
ClusterAPIAddress: state.ClusterAPIAddress,
|
ClusterAPIAddress: state.ClusterAPIAddress,
|
||||||
CoreAPIAddress: state.CoreAPIAddress,
|
CoreAPIAddress: state.CoreAPIAddress,
|
||||||
Server: []api.ClusterServer{},
|
Raft: api.ClusterRaft{
|
||||||
Stats: api.ClusterStats{
|
Server: []api.ClusterRaftServer{},
|
||||||
State: state.Stats.State,
|
Stats: api.ClusterRaftStats{
|
||||||
LastContact: state.Stats.LastContact.Seconds() * 1000,
|
State: state.Raft.Stats.State,
|
||||||
NumPeers: state.Stats.NumPeers,
|
LastContact: state.Raft.Stats.LastContact.Seconds() * 1000,
|
||||||
|
NumPeers: state.Raft.Stats.NumPeers,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
Version: state.Version.String(),
|
||||||
|
Degraded: state.Degraded,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, n := range state.Nodes {
|
for _, n := range state.Raft.Server {
|
||||||
about.Server = append(about.Server, api.ClusterServer{
|
about.Raft.Server = append(about.Raft.Server, api.ClusterRaftServer{
|
||||||
ID: n.ID,
|
ID: n.ID,
|
||||||
Address: n.Address,
|
Address: n.Address,
|
||||||
Voter: n.Voter,
|
Voter: n.Voter,
|
||||||
@@ -82,6 +86,13 @@ func (h *ClusterHandler) About(c echo.Context) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, node := range state.Nodes {
|
||||||
|
n := api.ClusterNode{}
|
||||||
|
n.Marshal(node)
|
||||||
|
|
||||||
|
about.Nodes = append(about.Nodes, n)
|
||||||
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, about)
|
return c.JSON(http.StatusOK, about)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,17 +170,8 @@ func (h *ClusterHandler) GetNodes(c echo.Context) error {
|
|||||||
|
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
about := node.About()
|
about := node.About()
|
||||||
n := api.ClusterNode{
|
n := api.ClusterNode{}
|
||||||
ID: about.ID,
|
n.Marshal(about)
|
||||||
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),
|
|
||||||
}
|
|
||||||
|
|
||||||
list = append(list, n)
|
list = append(list, n)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user