Merge branch 'vod' into vod-auto-remove-node
This commit is contained in:
commit
8dcda07fc1
@ -544,6 +544,7 @@ func (a *api) start(ctx context.Context) error {
|
||||
CoreSkills: a.ffmpeg.Skills(),
|
||||
IPLimiter: a.sessionsLimiter,
|
||||
Logger: a.log.logger.core.WithComponent("Cluster"),
|
||||
Resources: a.resources,
|
||||
Debug: cluster.DebugConfig{
|
||||
DisableFFmpegCheck: cfg.Cluster.Debug.DisableFFmpegCheck,
|
||||
},
|
||||
@ -1338,7 +1339,7 @@ func (a *api) start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if a.cluster != nil {
|
||||
config.Proxy = a.cluster.ProxyReader()
|
||||
config.Proxy = a.cluster.Manager()
|
||||
}
|
||||
|
||||
if cfg.RTMP.EnableTLS {
|
||||
@ -1369,7 +1370,7 @@ func (a *api) start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if a.cluster != nil {
|
||||
config.Proxy = a.cluster.ProxyReader()
|
||||
config.Proxy = a.cluster.Manager()
|
||||
}
|
||||
|
||||
if cfg.SRT.Log.Enable {
|
||||
|
||||
173
cluster/about.go
Normal file
173
cluster/about.go
Normal file
@ -0,0 +1,173 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster/raft"
|
||||
"github.com/datarhei/core/v16/slices"
|
||||
)
|
||||
|
||||
type ClusterRaft struct {
|
||||
Address string
|
||||
State string
|
||||
LastContact time.Duration
|
||||
NumPeers uint64
|
||||
LogTerm uint64
|
||||
LogIndex uint64
|
||||
}
|
||||
|
||||
type ClusterNodeResources struct {
|
||||
IsThrottling bool // Whether this core is currently throttling
|
||||
NCPU float64 // Number of CPU on this node
|
||||
CPU float64 // Current CPU load, 0-100*ncpu
|
||||
CPULimit float64 // Defined CPU load limit, 0-100*ncpu
|
||||
Mem uint64 // Currently used memory in bytes
|
||||
MemLimit uint64 // Defined memory limit in bytes
|
||||
Error error
|
||||
}
|
||||
|
||||
type ClusterNode struct {
|
||||
ID string
|
||||
Name string
|
||||
Version string
|
||||
State string
|
||||
Error error
|
||||
Voter bool
|
||||
Leader bool
|
||||
Address string
|
||||
CreatedAt time.Time
|
||||
Uptime time.Duration
|
||||
LastContact time.Duration
|
||||
Latency time.Duration
|
||||
Core ClusterNodeCore
|
||||
Resources ClusterNodeResources
|
||||
}
|
||||
|
||||
type ClusterNodeCore struct {
|
||||
Address string
|
||||
State string
|
||||
Error error
|
||||
LastContact time.Duration
|
||||
Latency time.Duration
|
||||
Version string
|
||||
}
|
||||
|
||||
type ClusterAboutLeader struct {
|
||||
ID string
|
||||
Address string
|
||||
ElectedSince time.Duration
|
||||
}
|
||||
|
||||
type ClusterAbout struct {
|
||||
ID string
|
||||
Domains []string
|
||||
Leader ClusterAboutLeader
|
||||
State string
|
||||
Raft ClusterRaft
|
||||
Nodes []ClusterNode
|
||||
Version ClusterVersion
|
||||
Error error
|
||||
}
|
||||
|
||||
func (c *cluster) About() (ClusterAbout, error) {
|
||||
c.stateLock.RLock()
|
||||
hasLeader := c.hasRaftLeader
|
||||
domains := slices.Copy(c.hostnames)
|
||||
c.stateLock.RUnlock()
|
||||
|
||||
about := ClusterAbout{
|
||||
ID: c.id,
|
||||
Leader: ClusterAboutLeader{},
|
||||
State: "online",
|
||||
Version: Version,
|
||||
Domains: domains,
|
||||
}
|
||||
|
||||
if !hasLeader {
|
||||
about.State = "offline"
|
||||
about.Error = errors.New("no raft leader elected")
|
||||
}
|
||||
|
||||
stats := c.raft.Stats()
|
||||
|
||||
about.Raft.Address = stats.Address
|
||||
about.Raft.State = stats.State
|
||||
about.Raft.LastContact = stats.LastContact
|
||||
about.Raft.NumPeers = stats.NumPeers
|
||||
about.Raft.LogIndex = stats.LogIndex
|
||||
about.Raft.LogTerm = stats.LogTerm
|
||||
|
||||
servers, err := c.raft.Servers()
|
||||
if err != nil {
|
||||
c.logger.Warn().WithError(err).Log("Raft configuration")
|
||||
}
|
||||
|
||||
serversMap := map[string]raft.Server{}
|
||||
|
||||
for _, s := range servers {
|
||||
serversMap[s.ID] = s
|
||||
|
||||
if s.Leader {
|
||||
about.Leader.ID = s.ID
|
||||
about.Leader.Address = s.Address
|
||||
about.Leader.ElectedSince = s.LastChange
|
||||
}
|
||||
}
|
||||
|
||||
storeNodes := c.ListNodes()
|
||||
nodes := c.manager.NodeList()
|
||||
|
||||
for _, node := range nodes {
|
||||
nodeAbout := node.About()
|
||||
|
||||
node := ClusterNode{
|
||||
ID: nodeAbout.ID,
|
||||
Name: nodeAbout.Name,
|
||||
Version: nodeAbout.Version,
|
||||
State: nodeAbout.State,
|
||||
Error: nodeAbout.Error,
|
||||
Address: nodeAbout.Address,
|
||||
LastContact: time.Since(nodeAbout.LastContact),
|
||||
Latency: nodeAbout.Latency,
|
||||
CreatedAt: nodeAbout.Core.CreatedAt,
|
||||
Uptime: nodeAbout.Core.Uptime,
|
||||
Core: ClusterNodeCore{
|
||||
Address: nodeAbout.Core.Address,
|
||||
State: nodeAbout.Core.State,
|
||||
Error: nodeAbout.Core.Error,
|
||||
LastContact: time.Since(nodeAbout.Core.LastContact),
|
||||
Latency: nodeAbout.Core.Latency,
|
||||
Version: nodeAbout.Core.Version.Number,
|
||||
},
|
||||
Resources: ClusterNodeResources{
|
||||
IsThrottling: nodeAbout.Resources.IsThrottling,
|
||||
NCPU: nodeAbout.Resources.NCPU,
|
||||
CPU: nodeAbout.Resources.CPU,
|
||||
CPULimit: nodeAbout.Resources.CPULimit,
|
||||
Mem: nodeAbout.Resources.Mem,
|
||||
MemLimit: nodeAbout.Resources.MemLimit,
|
||||
Error: nodeAbout.Resources.Error,
|
||||
},
|
||||
}
|
||||
|
||||
if s, ok := serversMap[nodeAbout.ID]; ok {
|
||||
node.Voter = s.Voter
|
||||
node.Leader = s.Leader
|
||||
}
|
||||
|
||||
if storeNode, hasStoreNode := storeNodes[nodeAbout.ID]; hasStoreNode {
|
||||
if storeNode.State == "maintenance" {
|
||||
node.State = storeNode.State
|
||||
}
|
||||
}
|
||||
|
||||
if about.State == "online" && node.State != "online" {
|
||||
about.State = "degraded"
|
||||
}
|
||||
|
||||
about.Nodes = append(about.Nodes, node)
|
||||
}
|
||||
|
||||
return about, nil
|
||||
}
|
||||
181
cluster/affinity.go
Normal file
181
cluster/affinity.go
Normal file
@ -0,0 +1,181 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
)
|
||||
|
||||
type referenceAffinityNodeCount struct {
|
||||
nodeid string
|
||||
count uint64
|
||||
}
|
||||
|
||||
type referenceAffinity struct {
|
||||
m map[string][]referenceAffinityNodeCount
|
||||
}
|
||||
|
||||
// NewReferenceAffinity returns a referenceAffinity. This is a map of references (per domain) to an array of
|
||||
// nodes this reference is found on and their count.
|
||||
func NewReferenceAffinity(processes []node.Process) *referenceAffinity {
|
||||
ra := &referenceAffinity{
|
||||
m: map[string][]referenceAffinityNodeCount{},
|
||||
}
|
||||
|
||||
for _, p := range processes {
|
||||
if len(p.Config.Reference) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := p.Config.Reference + "@" + p.Config.Domain
|
||||
|
||||
// Here we count how often a reference is present on a node. When
|
||||
// moving processes to a different node, the node with the highest
|
||||
// count of same references will be the first candidate.
|
||||
found := false
|
||||
arr := ra.m[key]
|
||||
for i, count := range arr {
|
||||
if count.nodeid == p.NodeID {
|
||||
count.count++
|
||||
arr[i] = count
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
arr = append(arr, referenceAffinityNodeCount{
|
||||
nodeid: p.NodeID,
|
||||
count: 1,
|
||||
})
|
||||
}
|
||||
|
||||
ra.m[key] = arr
|
||||
}
|
||||
|
||||
// Sort every reference count in decreasing order for each reference.
|
||||
for ref, count := range ra.m {
|
||||
sort.SliceStable(count, func(a, b int) bool {
|
||||
return count[a].count > count[b].count
|
||||
})
|
||||
|
||||
ra.m[ref] = count
|
||||
}
|
||||
|
||||
return ra
|
||||
}
|
||||
|
||||
// Nodes returns a list of node IDs for the provided reference and domain. The list
|
||||
// is ordered by how many references are on the nodes in descending order.
|
||||
func (ra *referenceAffinity) Nodes(reference, domain string) []string {
|
||||
if len(reference) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
key := reference + "@" + domain
|
||||
|
||||
counts, ok := ra.m[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
nodes := []string{}
|
||||
|
||||
for _, count := range counts {
|
||||
nodes = append(nodes, count.nodeid)
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
// Add adds a reference on a node to an existing reference affinity.
|
||||
func (ra *referenceAffinity) Add(reference, domain, nodeid string) {
|
||||
if len(reference) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
key := reference + "@" + domain
|
||||
|
||||
counts, ok := ra.m[key]
|
||||
if !ok {
|
||||
ra.m[key] = []referenceAffinityNodeCount{
|
||||
{
|
||||
nodeid: nodeid,
|
||||
count: 1,
|
||||
},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
found := false
|
||||
for i, count := range counts {
|
||||
if count.nodeid == nodeid {
|
||||
count.count++
|
||||
counts[i] = count
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
counts = append(counts, referenceAffinityNodeCount{
|
||||
nodeid: nodeid,
|
||||
count: 1,
|
||||
})
|
||||
}
|
||||
|
||||
ra.m[key] = counts
|
||||
}
|
||||
|
||||
// Move moves a reference from one node to another node in an existing reference affinity.
|
||||
func (ra *referenceAffinity) Move(reference, domain, fromnodeid, tonodeid string) {
|
||||
if len(reference) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
key := reference + "@" + domain
|
||||
|
||||
counts, ok := ra.m[key]
|
||||
if !ok {
|
||||
ra.m[key] = []referenceAffinityNodeCount{
|
||||
{
|
||||
nodeid: tonodeid,
|
||||
count: 1,
|
||||
},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
found := false
|
||||
for i, count := range counts {
|
||||
if count.nodeid == tonodeid {
|
||||
count.count++
|
||||
counts[i] = count
|
||||
found = true
|
||||
} else if count.nodeid == fromnodeid {
|
||||
count.count--
|
||||
counts[i] = count
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
counts = append(counts, referenceAffinityNodeCount{
|
||||
nodeid: tonodeid,
|
||||
count: 1,
|
||||
})
|
||||
}
|
||||
|
||||
newCounts := []referenceAffinityNodeCount{}
|
||||
|
||||
for _, count := range counts {
|
||||
if count.count == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
newCounts = append(newCounts, count)
|
||||
}
|
||||
|
||||
ra.m[key] = newCounts
|
||||
}
|
||||
316
cluster/api.go
316
cluster/api.go
File diff suppressed because it is too large
Load Diff
@ -4,15 +4,14 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/config"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/ffmpeg/skills"
|
||||
httpapi "github.com/datarhei/core/v16/http/api"
|
||||
iamaccess "github.com/datarhei/core/v16/iam/access"
|
||||
iamidentity "github.com/datarhei/core/v16/iam/identity"
|
||||
"github.com/datarhei/core/v16/restream/app"
|
||||
@ -39,6 +38,10 @@ type SetProcessMetadataRequest struct {
|
||||
Metadata interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
type RelocateProcessesRequest struct {
|
||||
Map map[app.ProcessID]string `json:"map"`
|
||||
}
|
||||
|
||||
type AddIdentityRequest struct {
|
||||
Identity iamidentity.User `json:"identity"`
|
||||
}
|
||||
@ -66,6 +69,28 @@ type GetKVResponse struct {
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type AboutResponse struct {
|
||||
ID string `json:"id"`
|
||||
Version string `json:"version"`
|
||||
Address string `json:"address"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
Resources AboutResponseResources `json:"resources"`
|
||||
}
|
||||
|
||||
type AboutResponseResources struct {
|
||||
IsThrottling bool `json:"is_throttling"` // Whether this core is currently throttling
|
||||
NCPU float64 `json:"ncpu"` // Number of CPU on this node
|
||||
CPU float64 `json:"cpu"` // Current CPU load, 0-100*ncpu
|
||||
CPULimit float64 `json:"cpu_limit"` // Defined CPU load limit, 0-100*ncpu
|
||||
Mem uint64 `json:"memory_bytes"` // Currently used memory in bytes
|
||||
MemLimit uint64 `json:"memory_limit_bytes"` // Defined memory limit in bytes
|
||||
Error string `json:"error"` // Last error
|
||||
}
|
||||
|
||||
type SetNodeStateRequest struct {
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
type APIClient struct {
|
||||
Address string
|
||||
Client *http.Client
|
||||
@ -86,8 +111,23 @@ func (c *APIClient) Version() (string, error) {
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (c *APIClient) About() (AboutResponse, error) {
|
||||
data, err := c.call(http.MethodGet, "/v1/about", "", nil, "")
|
||||
if err != nil {
|
||||
return AboutResponse{}, err
|
||||
}
|
||||
|
||||
var about AboutResponse
|
||||
err = json.Unmarshal(data, &about)
|
||||
if err != nil {
|
||||
return AboutResponse{}, err
|
||||
}
|
||||
|
||||
return about, nil
|
||||
}
|
||||
|
||||
func (c *APIClient) Barrier(name string) (bool, error) {
|
||||
data, err := c.call(http.MethodGet, "/v1/barrier/"+url.PathEscape(name), "application/json", nil, "")
|
||||
data, err := c.call(http.MethodGet, "/v1/barrier/"+url.PathEscape(name), "", nil, "")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -169,155 +209,6 @@ func (c *APIClient) TransferLeadership(origin, id string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) AddProcess(origin string, r AddProcessRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPost, "/v1/process", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) RemoveProcess(origin string, id app.ProcessID) error {
|
||||
_, err := c.call(http.MethodDelete, "/v1/process/"+url.PathEscape(id.ID)+"?domain="+url.QueryEscape(id.Domain), "application/json", nil, origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) UpdateProcess(origin string, id app.ProcessID, r UpdateProcessRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/process/"+url.PathEscape(id.ID)+"?domain="+url.QueryEscape(id.Domain), "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) SetProcessCommand(origin string, id app.ProcessID, r SetProcessCommandRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/process/"+url.PathEscape(id.ID)+"/command?domain="+url.QueryEscape(id.Domain), "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) SetProcessMetadata(origin string, id app.ProcessID, key string, r SetProcessMetadataRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/process/"+url.PathEscape(id.ID)+"/metadata/"+url.PathEscape(key)+"?domain="+url.QueryEscape(id.Domain), "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) AddIdentity(origin string, r AddIdentityRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPost, "/v1/iam/user", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) UpdateIdentity(origin, name string, r UpdateIdentityRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/iam/user/"+url.PathEscape(name), "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) SetPolicies(origin, name string, r SetPoliciesRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/iam/user/"+url.PathEscape(name)+"/policies", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) RemoveIdentity(origin string, name string) error {
|
||||
_, err := c.call(http.MethodDelete, "/v1/iam/user/"+url.PathEscape(name), "application/json", nil, origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) Lock(origin string, r LockRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPost, "/v1/lock", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) Unlock(origin string, name string) error {
|
||||
_, err := c.call(http.MethodDelete, "/v1/lock/"+url.PathEscape(name), "application/json", nil, origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) SetKV(origin string, r SetKVRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPost, "/v1/kv", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) UnsetKV(origin string, key string) error {
|
||||
_, err := c.call(http.MethodDelete, "/v1/kv/"+url.PathEscape(key), "application/json", nil, origin)
|
||||
if err != nil {
|
||||
e, ok := err.(httpapi.Error)
|
||||
if ok && e.Code == 404 {
|
||||
return fs.ErrNotExist
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) GetKV(origin string, key string) (string, time.Time, error) {
|
||||
data, err := c.call(http.MethodGet, "/v1/kv/"+url.PathEscape(key), "application/json", nil, origin)
|
||||
if err != nil {
|
||||
e, ok := err.(httpapi.Error)
|
||||
if ok && e.Code == 404 {
|
||||
return "", time.Time{}, fs.ErrNotExist
|
||||
}
|
||||
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
res := GetKVResponse{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
return res.Value, res.UpdatedAt, nil
|
||||
}
|
||||
|
||||
func (c *APIClient) Snapshot(origin string) (io.ReadCloser, error) {
|
||||
return c.stream(http.MethodGet, "/v1/snapshot", "", nil, origin)
|
||||
}
|
||||
@ -346,7 +237,7 @@ func (c *APIClient) stream(method, path, contentType string, data io.Reader, ori
|
||||
}
|
||||
|
||||
if status < 200 || status >= 300 {
|
||||
e := httpapi.Error{}
|
||||
e := Error{}
|
||||
|
||||
defer body.Close()
|
||||
|
||||
@ -392,3 +283,14 @@ func (c *APIClient) request(req *http.Request) (int, io.ReadCloser, error) {
|
||||
|
||||
return resp.StatusCode, resp.Body, nil
|
||||
}
|
||||
|
||||
// Error represents an error response of the API
|
||||
type Error struct {
|
||||
Code int `json:"code" jsonschema:"required" format:"int"`
|
||||
Message string `json:"message" jsonschema:""`
|
||||
Details []string `json:"details" jsonschema:""`
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return strings.Join(e.Details, ", ")
|
||||
}
|
||||
|
||||
48
cluster/client/iam.go
Normal file
48
cluster/client/iam.go
Normal file
@ -0,0 +1,48 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
)
|
||||
|
||||
func (c *APIClient) IAMIdentityAdd(origin string, r AddIdentityRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPost, "/v1/iam/user", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) IAMIdentityUpdate(origin, name string, r UpdateIdentityRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/iam/user/"+url.PathEscape(name), "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) IAMPoliciesSet(origin, name string, r SetPoliciesRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/iam/user/"+url.PathEscape(name)+"/policies", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) IAMIdentityRemove(origin string, name string) error {
|
||||
_, err := c.call(http.MethodDelete, "/v1/iam/user/"+url.PathEscape(name), "application/json", nil, origin)
|
||||
|
||||
return err
|
||||
}
|
||||
54
cluster/client/kvs.go
Normal file
54
cluster/client/kvs.go
Normal file
@ -0,0 +1,54 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
)
|
||||
|
||||
func (c *APIClient) KVSet(origin string, r SetKVRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPost, "/v1/kv", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) KVUnset(origin string, key string) error {
|
||||
_, err := c.call(http.MethodDelete, "/v1/kv/"+url.PathEscape(key), "application/json", nil, origin)
|
||||
if err != nil {
|
||||
e, ok := err.(Error)
|
||||
if ok && e.Code == 404 {
|
||||
return fs.ErrNotExist
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) KVGet(origin string, key string) (string, time.Time, error) {
|
||||
data, err := c.call(http.MethodGet, "/v1/kv/"+url.PathEscape(key), "application/json", nil, origin)
|
||||
if err != nil {
|
||||
e, ok := err.(Error)
|
||||
if ok && e.Code == 404 {
|
||||
return "", time.Time{}, fs.ErrNotExist
|
||||
}
|
||||
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
res := GetKVResponse{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
return res.Value, res.UpdatedAt, nil
|
||||
}
|
||||
26
cluster/client/lock.go
Normal file
26
cluster/client/lock.go
Normal file
@ -0,0 +1,26 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
)
|
||||
|
||||
func (c *APIClient) LockCreate(origin string, r LockRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPost, "/v1/lock", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) LockDelete(origin string, name string) error {
|
||||
_, err := c.call(http.MethodDelete, "/v1/lock/"+url.PathEscape(name), "application/json", nil, origin)
|
||||
|
||||
return err
|
||||
}
|
||||
20
cluster/client/node.go
Normal file
20
cluster/client/node.go
Normal file
@ -0,0 +1,20 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
)
|
||||
|
||||
func (c *APIClient) NodeSetState(origin string, nodeid string, r SetNodeStateRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/node/"+url.PathEscape(nodeid)+"/state", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
71
cluster/client/proces.go
Normal file
71
cluster/client/proces.go
Normal file
@ -0,0 +1,71 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/restream/app"
|
||||
)
|
||||
|
||||
func (c *APIClient) ProcessAdd(origin string, r AddProcessRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPost, "/v1/process", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) ProcessRemove(origin string, id app.ProcessID) error {
|
||||
_, err := c.call(http.MethodDelete, "/v1/process/"+url.PathEscape(id.ID)+"?domain="+url.QueryEscape(id.Domain), "application/json", nil, origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) ProcessUpdate(origin string, id app.ProcessID, r UpdateProcessRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/process/"+url.PathEscape(id.ID)+"?domain="+url.QueryEscape(id.Domain), "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) ProcessSetCommand(origin string, id app.ProcessID, r SetProcessCommandRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/process/"+url.PathEscape(id.ID)+"/command?domain="+url.QueryEscape(id.Domain), "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) ProcessSetMetadata(origin string, id app.ProcessID, key string, r SetProcessMetadataRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/process/"+url.PathEscape(id.ID)+"/metadata/"+url.PathEscape(key)+"?domain="+url.QueryEscape(id.Domain), "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIClient) ProcessesRelocate(origin string, r RelocateProcessesRequest) error {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.call(http.MethodPut, "/v1/relocate", "application/json", bytes.NewReader(data), origin)
|
||||
|
||||
return err
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -626,6 +626,62 @@ const docTemplateClusterAPI = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/node/{id}/state": {
|
||||
"get": {
|
||||
"description": "Set a state for a node",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v1.0.0"
|
||||
],
|
||||
"summary": "Set a state for a node",
|
||||
"operationId": "cluster-1-node-set-state",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Set node state request",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/client.SetNodeStateRequest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Origin ID of request",
|
||||
"name": "X-Cluster-Origin",
|
||||
"in": "header"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
},
|
||||
"508": {
|
||||
"description": "Loop Detected",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/process": {
|
||||
"post": {
|
||||
"description": "Add a process to the cluster DB",
|
||||
@ -918,6 +974,50 @@ const docTemplateClusterAPI = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/relocate": {
|
||||
"put": {
|
||||
"description": "Relocate processes to another node.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v1.0.0"
|
||||
],
|
||||
"summary": "Relocate processes to another node",
|
||||
"operationId": "cluster-3-relocate-processes",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "List of processes to relocate",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/client.RelocateProcessesRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
},
|
||||
"508": {
|
||||
"description": "Loop Detected",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/server": {
|
||||
"post": {
|
||||
"description": "Add a new server to the cluster",
|
||||
@ -1281,6 +1381,17 @@ const docTemplateClusterAPI = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"client.RelocateProcessesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"map": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"client.SetKVRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -1292,6 +1403,14 @@ const docTemplateClusterAPI = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"client.SetNodeStateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"client.SetPoliciesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@ -618,6 +618,62 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/node/{id}/state": {
|
||||
"get": {
|
||||
"description": "Set a state for a node",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v1.0.0"
|
||||
],
|
||||
"summary": "Set a state for a node",
|
||||
"operationId": "cluster-1-node-set-state",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Set node state request",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/client.SetNodeStateRequest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Origin ID of request",
|
||||
"name": "X-Cluster-Origin",
|
||||
"in": "header"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
},
|
||||
"508": {
|
||||
"description": "Loop Detected",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/process": {
|
||||
"post": {
|
||||
"description": "Add a process to the cluster DB",
|
||||
@ -910,6 +966,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/relocate": {
|
||||
"put": {
|
||||
"description": "Relocate processes to another node.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v1.0.0"
|
||||
],
|
||||
"summary": "Relocate processes to another node",
|
||||
"operationId": "cluster-3-relocate-processes",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "List of processes to relocate",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/client.RelocateProcessesRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
},
|
||||
"508": {
|
||||
"description": "Loop Detected",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/cluster.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/server": {
|
||||
"post": {
|
||||
"description": "Add a new server to the cluster",
|
||||
@ -1273,6 +1373,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"client.RelocateProcessesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"map": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"client.SetKVRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -1284,6 +1395,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"client.SetNodeStateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"client.SetPoliciesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@ -122,6 +122,13 @@ definitions:
|
||||
valid_until:
|
||||
type: string
|
||||
type: object
|
||||
client.RelocateProcessesRequest:
|
||||
properties:
|
||||
map:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
client.SetKVRequest:
|
||||
properties:
|
||||
key:
|
||||
@ -129,6 +136,11 @@ definitions:
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
client.SetNodeStateRequest:
|
||||
properties:
|
||||
state:
|
||||
type: string
|
||||
type: object
|
||||
client.SetPoliciesRequest:
|
||||
properties:
|
||||
policies:
|
||||
@ -1268,6 +1280,43 @@ paths:
|
||||
summary: Remove a lock
|
||||
tags:
|
||||
- v1.0.0
|
||||
/v1/node/{id}/state:
|
||||
get:
|
||||
description: Set a state for a node
|
||||
operationId: cluster-1-node-set-state
|
||||
parameters:
|
||||
- description: Set node state request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/client.SetNodeStateRequest'
|
||||
- description: Origin ID of request
|
||||
in: header
|
||||
name: X-Cluster-Origin
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/cluster.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/cluster.Error'
|
||||
"508":
|
||||
description: Loop Detected
|
||||
schema:
|
||||
$ref: '#/definitions/cluster.Error'
|
||||
summary: Set a state for a node
|
||||
tags:
|
||||
- v1.0.0
|
||||
/v1/process:
|
||||
post:
|
||||
consumes:
|
||||
@ -1466,6 +1515,35 @@ paths:
|
||||
summary: Add JSON metadata with a process under the given key
|
||||
tags:
|
||||
- v1.0.0
|
||||
/v1/relocate:
|
||||
put:
|
||||
description: Relocate processes to another node.
|
||||
operationId: cluster-3-relocate-processes
|
||||
parameters:
|
||||
- description: List of processes to relocate
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/client.RelocateProcessesRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/cluster.Error'
|
||||
"508":
|
||||
description: Loop Detected
|
||||
schema:
|
||||
$ref: '#/definitions/cluster.Error'
|
||||
summary: Relocate processes to another node
|
||||
tags:
|
||||
- v1.0.0
|
||||
/v1/server:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
21
cluster/forwarder/errors.go
Normal file
21
cluster/forwarder/errors.go
Normal file
@ -0,0 +1,21 @@
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
apiclient "github.com/datarhei/core/v16/cluster/client"
|
||||
"github.com/datarhei/core/v16/cluster/store"
|
||||
)
|
||||
|
||||
func reconstructError(err error) error {
|
||||
if cerr, ok := err.(apiclient.Error); ok {
|
||||
switch cerr.Code {
|
||||
case 400:
|
||||
err = fmt.Errorf("%s%w", err.Error(), store.ErrBadRequest)
|
||||
case 404:
|
||||
err = fmt.Errorf("%s%w", err.Error(), store.ErrNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@ -7,63 +7,31 @@ import (
|
||||
"time"
|
||||
|
||||
apiclient "github.com/datarhei/core/v16/cluster/client"
|
||||
iamaccess "github.com/datarhei/core/v16/iam/access"
|
||||
iamidentity "github.com/datarhei/core/v16/iam/identity"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
"github.com/datarhei/core/v16/restream/app"
|
||||
)
|
||||
|
||||
// Forwarder forwards any HTTP request from a follower to the leader
|
||||
type Forwarder interface {
|
||||
SetLeader(address string)
|
||||
HasLeader() bool
|
||||
|
||||
Join(origin, id, raftAddress, peerAddress string) error
|
||||
Leave(origin, id string) error
|
||||
TransferLeadership(origin, id string) error
|
||||
Snapshot(origin string) (io.ReadCloser, error)
|
||||
|
||||
AddProcess(origin string, config *app.Config) error
|
||||
UpdateProcess(origin string, id app.ProcessID, config *app.Config) error
|
||||
RemoveProcess(origin string, id app.ProcessID) error
|
||||
SetProcessCommand(origin string, id app.ProcessID, command string) error
|
||||
SetProcessMetadata(origin string, id app.ProcessID, key string, data interface{}) error
|
||||
|
||||
AddIdentity(origin string, identity iamidentity.User) error
|
||||
UpdateIdentity(origin, name string, identity iamidentity.User) error
|
||||
SetPolicies(origin, name string, policies []iamaccess.Policy) error
|
||||
RemoveIdentity(origin string, name string) error
|
||||
|
||||
CreateLock(origin string, name string, validUntil time.Time) error
|
||||
DeleteLock(origin string, name string) error
|
||||
|
||||
SetKV(origin, key, value string) error
|
||||
UnsetKV(origin, key string) error
|
||||
GetKV(origin, key string) (string, time.Time, error)
|
||||
}
|
||||
|
||||
type forwarder struct {
|
||||
id string
|
||||
lock sync.RWMutex
|
||||
type Forwarder struct {
|
||||
ID string
|
||||
Logger log.Logger
|
||||
|
||||
lock sync.RWMutex
|
||||
client apiclient.APIClient
|
||||
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
type ForwarderConfig struct {
|
||||
type Config struct {
|
||||
ID string
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
func New(config ForwarderConfig) (Forwarder, error) {
|
||||
f := &forwarder{
|
||||
id: config.ID,
|
||||
logger: config.Logger,
|
||||
func New(config Config) (*Forwarder, error) {
|
||||
f := &Forwarder{
|
||||
ID: config.ID,
|
||||
Logger: config.Logger,
|
||||
}
|
||||
|
||||
if f.logger == nil {
|
||||
f.logger = log.New("")
|
||||
if f.Logger == nil {
|
||||
f.Logger = log.New("")
|
||||
}
|
||||
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
@ -82,7 +50,7 @@ func New(config ForwarderConfig) (Forwarder, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *forwarder) SetLeader(address string) {
|
||||
func (f *Forwarder) SetLeader(address string) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
@ -90,18 +58,18 @@ func (f *forwarder) SetLeader(address string) {
|
||||
return
|
||||
}
|
||||
|
||||
f.logger.Debug().Log("Setting leader address to %s", address)
|
||||
f.Logger.Debug().Log("Setting leader address to %s", address)
|
||||
|
||||
f.client.Address = address
|
||||
}
|
||||
|
||||
func (f *forwarder) HasLeader() bool {
|
||||
func (f *Forwarder) HasLeader() bool {
|
||||
return len(f.client.Address) != 0
|
||||
}
|
||||
|
||||
func (f *forwarder) Join(origin, id, raftAddress, peerAddress string) error {
|
||||
func (f *Forwarder) Join(origin, id, raftAddress, peerAddress string) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.JoinRequest{
|
||||
@ -109,7 +77,7 @@ func (f *forwarder) Join(origin, id, raftAddress, peerAddress string) error {
|
||||
RaftAddress: raftAddress,
|
||||
}
|
||||
|
||||
f.logger.Debug().WithField("request", r).Log("Forwarding to leader")
|
||||
f.Logger.Debug().WithField("request", r).Log("Forwarding to leader")
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
@ -125,12 +93,12 @@ func (f *forwarder) Join(origin, id, raftAddress, peerAddress string) error {
|
||||
return client.Join(origin, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) Leave(origin, id string) error {
|
||||
func (f *Forwarder) Leave(origin, id string) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
f.logger.Debug().WithField("id", id).Log("Forwarding to leader")
|
||||
f.Logger.Debug().WithField("id", id).Log("Forwarding to leader")
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
@ -139,12 +107,12 @@ func (f *forwarder) Leave(origin, id string) error {
|
||||
return client.Leave(origin, id)
|
||||
}
|
||||
|
||||
func (f *forwarder) TransferLeadership(origin, id string) error {
|
||||
func (f *Forwarder) TransferLeadership(origin, id string) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
f.logger.Debug().WithField("id", id).Log("Transferring leadership")
|
||||
f.Logger.Debug().WithField("id", id).Log("Transferring leadership")
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
@ -153,216 +121,10 @@ func (f *forwarder) TransferLeadership(origin, id string) error {
|
||||
return client.TransferLeadership(origin, id)
|
||||
}
|
||||
|
||||
func (f *forwarder) Snapshot(origin string) (io.ReadCloser, error) {
|
||||
func (f *Forwarder) Snapshot(origin string) (io.ReadCloser, error) {
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.Snapshot(origin)
|
||||
}
|
||||
|
||||
func (f *forwarder) AddProcess(origin string, config *app.Config) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
r := apiclient.AddProcessRequest{
|
||||
Config: *config,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.AddProcess(origin, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) UpdateProcess(origin string, id app.ProcessID, config *app.Config) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
r := apiclient.UpdateProcessRequest{
|
||||
Config: *config,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.UpdateProcess(origin, id, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) SetProcessCommand(origin string, id app.ProcessID, command string) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
r := apiclient.SetProcessCommandRequest{
|
||||
Command: command,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.SetProcessCommand(origin, id, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) SetProcessMetadata(origin string, id app.ProcessID, key string, data interface{}) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
r := apiclient.SetProcessMetadataRequest{
|
||||
Metadata: data,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.SetProcessMetadata(origin, id, key, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) RemoveProcess(origin string, id app.ProcessID) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.RemoveProcess(origin, id)
|
||||
}
|
||||
|
||||
func (f *forwarder) AddIdentity(origin string, identity iamidentity.User) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
r := apiclient.AddIdentityRequest{
|
||||
Identity: identity,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.AddIdentity(origin, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) UpdateIdentity(origin, name string, identity iamidentity.User) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
r := apiclient.UpdateIdentityRequest{
|
||||
Identity: identity,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.UpdateIdentity(origin, name, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) SetPolicies(origin, name string, policies []iamaccess.Policy) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
r := apiclient.SetPoliciesRequest{
|
||||
Policies: policies,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.SetPolicies(origin, name, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) RemoveIdentity(origin string, name string) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.RemoveIdentity(origin, name)
|
||||
}
|
||||
|
||||
func (f *forwarder) CreateLock(origin string, name string, validUntil time.Time) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
r := apiclient.LockRequest{
|
||||
Name: name,
|
||||
ValidUntil: validUntil,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.Lock(origin, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) DeleteLock(origin string, name string) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.Unlock(origin, name)
|
||||
}
|
||||
|
||||
func (f *forwarder) SetKV(origin, key, value string) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
r := apiclient.SetKVRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.SetKV(origin, r)
|
||||
}
|
||||
|
||||
func (f *forwarder) UnsetKV(origin, key string) error {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.UnsetKV(origin, key)
|
||||
}
|
||||
|
||||
func (f *forwarder) GetKV(origin, key string) (string, time.Time, error) {
|
||||
if origin == "" {
|
||||
origin = f.id
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return client.GetKV(origin, key)
|
||||
}
|
||||
|
||||
67
cluster/forwarder/iam.go
Normal file
67
cluster/forwarder/iam.go
Normal file
@ -0,0 +1,67 @@
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
apiclient "github.com/datarhei/core/v16/cluster/client"
|
||||
iamaccess "github.com/datarhei/core/v16/iam/access"
|
||||
iamidentity "github.com/datarhei/core/v16/iam/identity"
|
||||
)
|
||||
|
||||
func (f *Forwarder) IAMIdentityAdd(origin string, identity iamidentity.User) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.AddIdentityRequest{
|
||||
Identity: identity,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.IAMIdentityAdd(origin, r))
|
||||
}
|
||||
|
||||
func (f *Forwarder) IAMIdentityUpdate(origin, name string, identity iamidentity.User) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.UpdateIdentityRequest{
|
||||
Identity: identity,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.IAMIdentityUpdate(origin, name, r))
|
||||
}
|
||||
|
||||
func (f *Forwarder) IAMPoliciesSet(origin, name string, policies []iamaccess.Policy) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.SetPoliciesRequest{
|
||||
Policies: policies,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.IAMPoliciesSet(origin, name, r))
|
||||
}
|
||||
|
||||
func (f *Forwarder) IAMIdentityRemove(origin string, name string) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.IAMIdentityRemove(origin, name))
|
||||
}
|
||||
50
cluster/forwarder/kvs.go
Normal file
50
cluster/forwarder/kvs.go
Normal file
@ -0,0 +1,50 @@
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
apiclient "github.com/datarhei/core/v16/cluster/client"
|
||||
)
|
||||
|
||||
func (f *Forwarder) KVSet(origin, key, value string) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.SetKVRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.KVSet(origin, r))
|
||||
}
|
||||
|
||||
func (f *Forwarder) KVUnset(origin, key string) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.KVUnset(origin, key))
|
||||
}
|
||||
|
||||
func (f *Forwarder) KVGet(origin, key string) (string, time.Time, error) {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
value, at, err := client.KVGet(origin, key)
|
||||
|
||||
return value, at, reconstructError(err)
|
||||
}
|
||||
36
cluster/forwarder/lock.go
Normal file
36
cluster/forwarder/lock.go
Normal file
@ -0,0 +1,36 @@
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
apiclient "github.com/datarhei/core/v16/cluster/client"
|
||||
)
|
||||
|
||||
func (f *Forwarder) LockCreate(origin string, name string, validUntil time.Time) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.LockRequest{
|
||||
Name: name,
|
||||
ValidUntil: validUntil,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.LockCreate(origin, r))
|
||||
}
|
||||
|
||||
func (f *Forwarder) LockDelete(origin string, name string) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.LockDelete(origin, name))
|
||||
}
|
||||
21
cluster/forwarder/node.go
Normal file
21
cluster/forwarder/node.go
Normal file
@ -0,0 +1,21 @@
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
apiclient "github.com/datarhei/core/v16/cluster/client"
|
||||
)
|
||||
|
||||
func (f *Forwarder) NodeSetState(origin, nodeid, state string) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.SetNodeStateRequest{
|
||||
State: state,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.NodeSetState(origin, nodeid, r))
|
||||
}
|
||||
98
cluster/forwarder/process.go
Normal file
98
cluster/forwarder/process.go
Normal file
@ -0,0 +1,98 @@
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
apiclient "github.com/datarhei/core/v16/cluster/client"
|
||||
"github.com/datarhei/core/v16/restream/app"
|
||||
)
|
||||
|
||||
func (f *Forwarder) ProcessAdd(origin string, config *app.Config) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.AddProcessRequest{
|
||||
Config: *config,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.ProcessAdd(origin, r))
|
||||
}
|
||||
|
||||
func (f *Forwarder) ProcessUpdate(origin string, id app.ProcessID, config *app.Config) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.UpdateProcessRequest{
|
||||
Config: *config,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.ProcessUpdate(origin, id, r))
|
||||
}
|
||||
|
||||
func (f *Forwarder) ProcessSetCommand(origin string, id app.ProcessID, command string) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.SetProcessCommandRequest{
|
||||
Command: command,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.ProcessSetCommand(origin, id, r))
|
||||
}
|
||||
|
||||
func (f *Forwarder) ProcessSetMetadata(origin string, id app.ProcessID, key string, data interface{}) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.SetProcessMetadataRequest{
|
||||
Metadata: data,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.ProcessSetMetadata(origin, id, key, r))
|
||||
}
|
||||
|
||||
func (f *Forwarder) ProcessRemove(origin string, id app.ProcessID) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.ProcessRemove(origin, id))
|
||||
}
|
||||
|
||||
func (f *Forwarder) ProcessesRelocate(origin string, relocations map[app.ProcessID]string) error {
|
||||
if origin == "" {
|
||||
origin = f.ID
|
||||
}
|
||||
|
||||
r := apiclient.RelocateProcessesRequest{
|
||||
Map: relocations,
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
client := f.client
|
||||
f.lock.RUnlock()
|
||||
|
||||
return reconstructError(client.ProcessesRelocate(origin, r))
|
||||
}
|
||||
@ -39,13 +39,13 @@ func (c *cluster) IAM(superuser iamidentity.User, jwtRealm, jwtSecret string) (i
|
||||
}
|
||||
|
||||
func (c *cluster) ListIdentities() (time.Time, []iamidentity.User) {
|
||||
users := c.store.ListUsers()
|
||||
users := c.store.IAMIdentityList()
|
||||
|
||||
return users.UpdatedAt, users.Users
|
||||
}
|
||||
|
||||
func (c *cluster) ListIdentity(name string) (time.Time, iamidentity.User, error) {
|
||||
user := c.store.GetUser(name)
|
||||
user := c.store.IAMIdentityGet(name)
|
||||
|
||||
if len(user.Users) == 0 {
|
||||
return time.Time{}, iamidentity.User{}, fmt.Errorf("not found")
|
||||
@ -55,28 +55,24 @@ func (c *cluster) ListIdentity(name string) (time.Time, iamidentity.User, error)
|
||||
}
|
||||
|
||||
func (c *cluster) ListPolicies() (time.Time, []iamaccess.Policy) {
|
||||
policies := c.store.ListPolicies()
|
||||
policies := c.store.IAMPolicyList()
|
||||
|
||||
return policies.UpdatedAt, policies.Policies
|
||||
}
|
||||
|
||||
func (c *cluster) ListUserPolicies(name string) (time.Time, []iamaccess.Policy) {
|
||||
policies := c.store.ListUserPolicies(name)
|
||||
policies := c.store.IAMIdentityPolicyList(name)
|
||||
|
||||
return policies.UpdatedAt, policies.Policies
|
||||
}
|
||||
|
||||
func (c *cluster) AddIdentity(origin string, identity iamidentity.User) error {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) IAMIdentityAdd(origin string, identity iamidentity.User) error {
|
||||
if err := identity.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid identity: %w", err)
|
||||
}
|
||||
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.AddIdentity(origin, identity)
|
||||
return c.forwarder.IAMIdentityAdd(origin, identity)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -89,13 +85,9 @@ func (c *cluster) AddIdentity(origin string, identity iamidentity.User) error {
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) UpdateIdentity(origin, name string, identity iamidentity.User) error {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) IAMIdentityUpdate(origin, name string, identity iamidentity.User) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.UpdateIdentity(origin, name, identity)
|
||||
return c.forwarder.IAMIdentityUpdate(origin, name, identity)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -109,13 +101,9 @@ func (c *cluster) UpdateIdentity(origin, name string, identity iamidentity.User)
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) SetPolicies(origin, name string, policies []iamaccess.Policy) error {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) IAMPoliciesSet(origin, name string, policies []iamaccess.Policy) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.SetPolicies(origin, name, policies)
|
||||
return c.forwarder.IAMPoliciesSet(origin, name, policies)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -129,13 +117,9 @@ func (c *cluster) SetPolicies(origin, name string, policies []iamaccess.Policy)
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) RemoveIdentity(origin string, name string) error {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) IAMIdentityRemove(origin string, name string) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.RemoveIdentity(origin, name)
|
||||
return c.forwarder.IAMIdentityRemove(origin, name)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
|
||||
@ -18,7 +18,7 @@ func NewIdentityAdapter(store store.Store) (iamidentity.Adapter, error) {
|
||||
}
|
||||
|
||||
func (a *identityAdapter) LoadIdentities() ([]iamidentity.User, error) {
|
||||
users := a.store.ListUsers()
|
||||
users := a.store.IAMIdentityList()
|
||||
|
||||
return users.Users, nil
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ func NewPolicyAdapter(store store.Store) (iamaccess.Adapter, error) {
|
||||
}
|
||||
|
||||
func (a *policyAdapter) LoadPolicy(model model.Model) error {
|
||||
policies := a.store.ListPolicies()
|
||||
policies := a.store.IAMPolicyList()
|
||||
|
||||
rules := [][]string{}
|
||||
domains := map[string]struct{}{}
|
||||
|
||||
@ -10,13 +10,9 @@ import (
|
||||
"github.com/datarhei/core/v16/log"
|
||||
)
|
||||
|
||||
func (c *cluster) CreateLock(origin string, name string, validUntil time.Time) (*kvs.Lock, error) {
|
||||
if ok, _ := c.IsClusterDegraded(); ok {
|
||||
return nil, ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) LockCreate(origin string, name string, validUntil time.Time) (*kvs.Lock, error) {
|
||||
if !c.IsRaftLeader() {
|
||||
err := c.forwarder.CreateLock(origin, name, validUntil)
|
||||
err := c.forwarder.LockCreate(origin, name, validUntil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -28,7 +24,7 @@ func (c *cluster) CreateLock(origin string, name string, validUntil time.Time) (
|
||||
return l, nil
|
||||
}
|
||||
|
||||
if c.store.HasLock(name) {
|
||||
if c.store.LockHasLock(name) {
|
||||
return nil, fmt.Errorf("the lock '%s' already exists", name)
|
||||
}
|
||||
|
||||
@ -52,13 +48,9 @@ func (c *cluster) CreateLock(origin string, name string, validUntil time.Time) (
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (c *cluster) DeleteLock(origin string, name string) error {
|
||||
if ok, _ := c.IsClusterDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) LockDelete(origin string, name string) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.DeleteLock(origin, name)
|
||||
return c.forwarder.LockDelete(origin, name)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -71,17 +63,9 @@ func (c *cluster) DeleteLock(origin string, name string) error {
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) ListLocks() map[string]time.Time {
|
||||
return c.store.ListLocks()
|
||||
}
|
||||
|
||||
func (c *cluster) SetKV(origin, key, value string) error {
|
||||
if ok, _ := c.IsClusterDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) KVSet(origin, key, value string) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.SetKV(origin, key, value)
|
||||
return c.forwarder.KVSet(origin, key, value)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -95,13 +79,9 @@ func (c *cluster) SetKV(origin, key, value string) error {
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) UnsetKV(origin, key string) error {
|
||||
if ok, _ := c.IsClusterDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) KVUnset(origin, key string) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.UnsetKV(origin, key)
|
||||
return c.forwarder.KVUnset(origin, key)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -114,18 +94,14 @@ func (c *cluster) UnsetKV(origin, key string) error {
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) GetKV(origin, key string, stale bool) (string, time.Time, error) {
|
||||
func (c *cluster) KVGet(origin, key string, stale bool) (string, time.Time, error) {
|
||||
if !stale {
|
||||
if ok, _ := c.IsClusterDegraded(); ok {
|
||||
return "", time.Time{}, ErrDegraded
|
||||
}
|
||||
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.GetKV(origin, key)
|
||||
return c.forwarder.KVGet(origin, key)
|
||||
}
|
||||
}
|
||||
|
||||
value, err := c.store.GetFromKVS(key)
|
||||
value, err := c.store.KVSGetValue(key)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
@ -133,12 +109,6 @@ func (c *cluster) GetKV(origin, key string, stale bool) (string, time.Time, erro
|
||||
return value.Value, value.UpdatedAt, nil
|
||||
}
|
||||
|
||||
func (c *cluster) ListKV(prefix string) map[string]store.Value {
|
||||
storeValues := c.store.ListKVS(prefix)
|
||||
|
||||
return storeValues
|
||||
}
|
||||
|
||||
type ClusterKVS interface {
|
||||
kvs.KVS
|
||||
|
||||
@ -178,17 +148,17 @@ func (s *clusterKVS) CreateLock(name string, validUntil time.Time) (*kvs.Lock, e
|
||||
"name": name,
|
||||
"valid_until": validUntil,
|
||||
}).Log("Create lock")
|
||||
return s.cluster.CreateLock("", name, validUntil)
|
||||
return s.cluster.LockCreate("", name, validUntil)
|
||||
}
|
||||
|
||||
func (s *clusterKVS) DeleteLock(name string) error {
|
||||
s.logger.Debug().WithField("name", name).Log("Delete lock")
|
||||
return s.cluster.DeleteLock("", name)
|
||||
return s.cluster.LockDelete("", name)
|
||||
}
|
||||
|
||||
func (s *clusterKVS) ListLocks() map[string]time.Time {
|
||||
s.logger.Debug().Log("List locks")
|
||||
return s.cluster.ListLocks()
|
||||
return s.cluster.Store().LockList()
|
||||
}
|
||||
|
||||
func (s *clusterKVS) SetKV(key, value string) error {
|
||||
@ -196,12 +166,12 @@ func (s *clusterKVS) SetKV(key, value string) error {
|
||||
"key": key,
|
||||
"value": value,
|
||||
}).Log("Set KV")
|
||||
return s.cluster.SetKV("", key, value)
|
||||
return s.cluster.KVSet("", key, value)
|
||||
}
|
||||
|
||||
func (s *clusterKVS) UnsetKV(key string) error {
|
||||
s.logger.Debug().WithField("key", key).Log("Unset KV")
|
||||
return s.cluster.UnsetKV("", key)
|
||||
return s.cluster.KVUnset("", key)
|
||||
}
|
||||
|
||||
func (s *clusterKVS) GetKV(key string) (string, time.Time, error) {
|
||||
@ -213,10 +183,10 @@ func (s *clusterKVS) GetKV(key string) (string, time.Time, error) {
|
||||
"key": key,
|
||||
"stale": stale,
|
||||
}).Log("Get KV")
|
||||
return s.cluster.GetKV("", key, stale)
|
||||
return s.cluster.KVGet("", key, stale)
|
||||
}
|
||||
|
||||
func (s *clusterKVS) ListKV(prefix string) map[string]store.Value {
|
||||
s.logger.Debug().Log("List KV")
|
||||
return s.cluster.ListKV(prefix)
|
||||
return s.cluster.Store().KVSList(prefix)
|
||||
}
|
||||
|
||||
1021
cluster/leader.go
1021
cluster/leader.go
File diff suppressed because it is too large
Load Diff
185
cluster/leader_rebalance.go
Normal file
185
cluster/leader_rebalance.go
Normal file
@ -0,0 +1,185 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
"github.com/datarhei/core/v16/cluster/store"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
)
|
||||
|
||||
func (c *cluster) doRebalance(emergency bool, term uint64) {
|
||||
if emergency {
|
||||
// Don't rebalance in emergency mode.
|
||||
return
|
||||
}
|
||||
|
||||
logger := c.logger.WithField("term", term)
|
||||
|
||||
logger.Debug().WithField("emergency", emergency).Log("Rebalancing")
|
||||
|
||||
storeNodes := c.store.NodeList()
|
||||
have := c.manager.ClusterProcessList()
|
||||
nodes := c.manager.NodeList()
|
||||
|
||||
nodesMap := map[string]node.About{}
|
||||
|
||||
for _, node := range nodes {
|
||||
about := node.About()
|
||||
|
||||
if storeNode, hasStoreNode := storeNodes[about.ID]; hasStoreNode {
|
||||
about.State = storeNode.State
|
||||
}
|
||||
|
||||
nodesMap[about.ID] = about
|
||||
}
|
||||
|
||||
logger.Debug().WithFields(log.Fields{
|
||||
"have": have,
|
||||
"nodes": nodesMap,
|
||||
}).Log("Rebalance")
|
||||
|
||||
opStack, _ := rebalance(have, nodesMap)
|
||||
|
||||
errors := c.applyOpStack(opStack, term)
|
||||
|
||||
for _, e := range errors {
|
||||
// Only apply the command if the error is different.
|
||||
process, err := c.store.ProcessGet(e.processid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var errmessage string = ""
|
||||
|
||||
if e.err != nil {
|
||||
if process.Error == e.err.Error() {
|
||||
continue
|
||||
}
|
||||
|
||||
errmessage = e.err.Error()
|
||||
} else {
|
||||
if len(process.Error) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
Operation: store.OpSetProcessError,
|
||||
Data: store.CommandSetProcessError{
|
||||
ID: e.processid,
|
||||
Error: errmessage,
|
||||
},
|
||||
}
|
||||
|
||||
c.applyCommand(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// rebalance returns a list of operations that will move running processes away from nodes that are overloaded.
|
||||
func rebalance(have []node.Process, nodes map[string]node.About) ([]interface{}, map[string]node.Resources) {
|
||||
resources := NewResourcePlanner(nodes)
|
||||
|
||||
// Mark nodes as throttling where at least one process is still throttling
|
||||
for _, haveP := range have {
|
||||
if haveP.Throttling {
|
||||
resources.Throttling(haveP.NodeID, true)
|
||||
}
|
||||
}
|
||||
|
||||
// Group all running processes by node and sort them by their runtime in ascending order.
|
||||
nodeProcessMap := createNodeProcessMap(have)
|
||||
|
||||
// A map from the process reference to the nodes it is running on.
|
||||
haveReferenceAffinity := NewReferenceAffinity(have)
|
||||
|
||||
opStack := []interface{}{}
|
||||
|
||||
// Check if any of the nodes is overloaded.
|
||||
for id, r := range resources.Map() {
|
||||
// Ignore this node if the resource values are not reliable.
|
||||
if r.Error != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if node is overloaded.
|
||||
if r.CPU < r.CPULimit && r.Mem < r.MemLimit && !r.IsThrottling {
|
||||
continue
|
||||
}
|
||||
|
||||
// Move processes from this node to another node with enough free resources.
|
||||
// The processes are ordered ascending by their runtime.
|
||||
processes := nodeProcessMap[id]
|
||||
if len(processes) == 0 {
|
||||
// If there are no processes on that node, we can't do anything.
|
||||
continue
|
||||
}
|
||||
|
||||
overloadedNodeid := id
|
||||
|
||||
for i, p := range processes {
|
||||
availableNodeid := ""
|
||||
|
||||
// Try to move the process to a node where other processes with the same
|
||||
// reference currently reside.
|
||||
if len(p.Config.Reference) != 0 {
|
||||
raNodes := haveReferenceAffinity.Nodes(p.Config.Reference, p.Config.Domain)
|
||||
for _, raNodeid := range raNodes {
|
||||
// Do not move the process to the node it is currently on.
|
||||
if raNodeid == overloadedNodeid {
|
||||
continue
|
||||
}
|
||||
|
||||
if resources.HasNodeEnough(raNodeid, p.Config.LimitCPU, p.Config.LimitMemory) {
|
||||
availableNodeid = raNodeid
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the best node with enough resources available.
|
||||
if len(availableNodeid) == 0 {
|
||||
nodes := resources.FindBestNodes(p.Config.LimitCPU, p.Config.LimitMemory)
|
||||
for _, nodeid := range nodes {
|
||||
if nodeid == overloadedNodeid {
|
||||
continue
|
||||
}
|
||||
|
||||
availableNodeid = nodeid
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(availableNodeid) == 0 {
|
||||
// There's no other node with enough resources to take over this process.
|
||||
opStack = append(opStack, processOpSkip{
|
||||
nodeid: overloadedNodeid,
|
||||
processid: p.Config.ProcessID(),
|
||||
err: errNotEnoughResourcesForRebalancing,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
opStack = append(opStack, processOpMove{
|
||||
fromNodeid: overloadedNodeid,
|
||||
toNodeid: availableNodeid,
|
||||
config: p.Config,
|
||||
metadata: p.Metadata,
|
||||
order: p.Order,
|
||||
})
|
||||
|
||||
// Adjust the process.
|
||||
p.NodeID = availableNodeid
|
||||
processes[i] = p
|
||||
|
||||
// Adjust the resources.
|
||||
resources.Move(availableNodeid, overloadedNodeid, p.CPU, p.Mem)
|
||||
|
||||
// Adjust the reference affinity.
|
||||
haveReferenceAffinity.Move(p.Config.Reference, p.Config.Domain, overloadedNodeid, availableNodeid)
|
||||
|
||||
// Move only one process at a time.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return opStack, resources.Map()
|
||||
}
|
||||
206
cluster/leader_relocate.go
Normal file
206
cluster/leader_relocate.go
Normal file
@ -0,0 +1,206 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
"github.com/datarhei/core/v16/cluster/store"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
"github.com/datarhei/core/v16/restream/app"
|
||||
)
|
||||
|
||||
func (c *cluster) doRelocate(emergency bool, term uint64) {
|
||||
if emergency {
|
||||
// Don't relocate in emergency mode.
|
||||
return
|
||||
}
|
||||
|
||||
logger := c.logger.WithField("term", term)
|
||||
|
||||
logger.Debug().WithField("emergency", emergency).Log("Relocating")
|
||||
|
||||
relocateMap := c.store.ProcessGetRelocateMap()
|
||||
storeNodes := c.store.NodeList()
|
||||
have := c.manager.ClusterProcessList()
|
||||
nodes := c.manager.NodeList()
|
||||
|
||||
nodesMap := map[string]node.About{}
|
||||
|
||||
for _, node := range nodes {
|
||||
about := node.About()
|
||||
|
||||
if storeNode, hasStoreNode := storeNodes[about.ID]; hasStoreNode {
|
||||
about.State = storeNode.State
|
||||
}
|
||||
|
||||
nodesMap[about.ID] = about
|
||||
}
|
||||
|
||||
logger.Debug().WithFields(log.Fields{
|
||||
"relocate": relocate,
|
||||
"have": have,
|
||||
"nodes": nodesMap,
|
||||
}).Log("Rebalance")
|
||||
|
||||
opStack, _, relocatedProcessIDs := relocate(have, nodesMap, relocateMap)
|
||||
|
||||
errors := c.applyOpStack(opStack, term)
|
||||
|
||||
for _, e := range errors {
|
||||
// Only apply the command if the error is different.
|
||||
process, err := c.store.ProcessGet(e.processid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var errmessage string = ""
|
||||
|
||||
if e.err != nil {
|
||||
if process.Error == e.err.Error() {
|
||||
continue
|
||||
}
|
||||
|
||||
errmessage = e.err.Error()
|
||||
} else {
|
||||
if len(process.Error) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
Operation: store.OpSetProcessError,
|
||||
Data: store.CommandSetProcessError{
|
||||
ID: e.processid,
|
||||
Error: errmessage,
|
||||
},
|
||||
}
|
||||
|
||||
c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
cmd := store.CommandUnsetRelocateProcess{
|
||||
ID: []app.ProcessID{},
|
||||
}
|
||||
|
||||
for _, processid := range relocatedProcessIDs {
|
||||
cmd.ID = append(cmd.ID, app.ParseProcessID(processid))
|
||||
}
|
||||
|
||||
if len(cmd.ID) != 0 {
|
||||
c.applyCommand(&store.Command{
|
||||
Operation: store.OpUnsetRelocateProcess,
|
||||
Data: cmd,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// relocate returns a list of operations that will move deployed processes to different nodes.
|
||||
func relocate(have []node.Process, nodes map[string]node.About, relocateMap map[string]string) ([]interface{}, map[string]node.Resources, []string) {
|
||||
resources := NewResourcePlanner(nodes)
|
||||
|
||||
// Mark nodes as throttling where at least one process is still throttling
|
||||
for _, haveP := range have {
|
||||
if haveP.Throttling {
|
||||
resources.Throttling(haveP.NodeID, true)
|
||||
}
|
||||
}
|
||||
|
||||
relocatedProcessIDs := []string{}
|
||||
|
||||
// A map from the process reference to the nodes it is running on.
|
||||
haveReferenceAffinity := NewReferenceAffinity(have)
|
||||
|
||||
opStack := []interface{}{}
|
||||
|
||||
// Check for any requested relocations.
|
||||
for processid, targetNodeid := range relocateMap {
|
||||
process := node.Process{}
|
||||
|
||||
found := false
|
||||
for _, p := range have {
|
||||
if processid == p.Config.ProcessID().String() {
|
||||
process = p
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
relocatedProcessIDs = append(relocatedProcessIDs, processid)
|
||||
continue
|
||||
}
|
||||
|
||||
sourceNodeid := process.NodeID
|
||||
|
||||
if sourceNodeid == targetNodeid {
|
||||
relocatedProcessIDs = append(relocatedProcessIDs, processid)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(targetNodeid) != 0 {
|
||||
_, hasNode := nodes[targetNodeid]
|
||||
|
||||
if !hasNode || !resources.HasNodeEnough(targetNodeid, process.Config.LimitCPU, process.Config.LimitMemory) {
|
||||
targetNodeid = ""
|
||||
}
|
||||
}
|
||||
|
||||
if len(targetNodeid) == 0 {
|
||||
// Try to move the process to a node where other processes with the same
|
||||
// reference currently reside.
|
||||
if len(process.Config.Reference) != 0 {
|
||||
raNodes := haveReferenceAffinity.Nodes(process.Config.Reference, process.Config.Domain)
|
||||
for _, raNodeid := range raNodes {
|
||||
// Do not move the process to the node it is currently on.
|
||||
if raNodeid == sourceNodeid {
|
||||
continue
|
||||
}
|
||||
|
||||
if resources.HasNodeEnough(raNodeid, process.Config.LimitCPU, process.Config.LimitMemory) {
|
||||
targetNodeid = raNodeid
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the best node with enough resources available.
|
||||
if len(targetNodeid) == 0 {
|
||||
nodes := resources.FindBestNodes(process.Config.LimitCPU, process.Config.LimitMemory)
|
||||
for _, nodeid := range nodes {
|
||||
if nodeid == sourceNodeid {
|
||||
continue
|
||||
}
|
||||
|
||||
targetNodeid = nodeid
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(targetNodeid) == 0 {
|
||||
// There's no other node with enough resources to take over this process.
|
||||
opStack = append(opStack, processOpSkip{
|
||||
nodeid: sourceNodeid,
|
||||
processid: process.Config.ProcessID(),
|
||||
err: errNotEnoughResourcesForRelocating,
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
opStack = append(opStack, processOpMove{
|
||||
fromNodeid: sourceNodeid,
|
||||
toNodeid: targetNodeid,
|
||||
config: process.Config,
|
||||
metadata: process.Metadata,
|
||||
order: process.Order,
|
||||
})
|
||||
|
||||
// Adjust the resources.
|
||||
resources.Move(targetNodeid, sourceNodeid, process.CPU, process.Mem)
|
||||
|
||||
// Adjust the reference affinity.
|
||||
haveReferenceAffinity.Move(process.Config.Reference, process.Config.Domain, sourceNodeid, targetNodeid)
|
||||
|
||||
relocatedProcessIDs = append(relocatedProcessIDs, processid)
|
||||
}
|
||||
|
||||
return opStack, resources.Map(), relocatedProcessIDs
|
||||
}
|
||||
377
cluster/leader_synchronize.go
Normal file
377
cluster/leader_synchronize.go
Normal file
@ -0,0 +1,377 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
"github.com/datarhei/core/v16/cluster/store"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
)
|
||||
|
||||
func (c *cluster) doSynchronize(emergency bool, term uint64) {
|
||||
wish := c.store.ProcessGetNodeMap()
|
||||
want := c.store.ProcessList()
|
||||
storeNodes := c.store.NodeList()
|
||||
have := c.manager.ClusterProcessList()
|
||||
nodes := c.manager.NodeList()
|
||||
|
||||
logger := c.logger.WithField("term", term)
|
||||
|
||||
logger.Debug().WithField("emergency", emergency).Log("Synchronizing")
|
||||
|
||||
nodesMap := map[string]node.About{}
|
||||
|
||||
for _, node := range nodes {
|
||||
about := node.About()
|
||||
|
||||
if storeNode, hasStoreNode := storeNodes[about.ID]; hasStoreNode {
|
||||
about.State = storeNode.State
|
||||
}
|
||||
|
||||
nodesMap[about.ID] = about
|
||||
}
|
||||
|
||||
logger.Debug().WithFields(log.Fields{
|
||||
"want": want,
|
||||
"have": have,
|
||||
"nodes": nodesMap,
|
||||
}).Log("Synchronize")
|
||||
|
||||
opStack, _, reality := synchronize(wish, want, have, nodesMap, c.nodeRecoverTimeout)
|
||||
|
||||
if !emergency && !maps.Equal(wish, reality) {
|
||||
cmd := &store.Command{
|
||||
Operation: store.OpSetProcessNodeMap,
|
||||
Data: store.CommandSetProcessNodeMap{
|
||||
Map: reality,
|
||||
},
|
||||
}
|
||||
|
||||
c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
errors := c.applyOpStack(opStack, term)
|
||||
|
||||
if !emergency {
|
||||
for _, e := range errors {
|
||||
// Only apply the command if the error is different.
|
||||
process, err := c.store.ProcessGet(e.processid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var errmessage string = ""
|
||||
|
||||
if e.err != nil {
|
||||
if process.Error == e.err.Error() {
|
||||
continue
|
||||
}
|
||||
|
||||
errmessage = e.err.Error()
|
||||
} else {
|
||||
if len(process.Error) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
Operation: store.OpSetProcessError,
|
||||
Data: store.CommandSetProcessError{
|
||||
ID: e.processid,
|
||||
Error: errmessage,
|
||||
},
|
||||
}
|
||||
|
||||
c.applyCommand(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isMetadataUpdateRequired compares two metadata. It relies on the documented property that json.Marshal
|
||||
// sorts the map keys prior encoding.
|
||||
func isMetadataUpdateRequired(wantMap map[string]interface{}, haveMap map[string]interface{}) (bool, map[string]interface{}) {
|
||||
hasChanges := false
|
||||
changeMap := map[string]interface{}{}
|
||||
|
||||
haveMapKeys := map[string]struct{}{}
|
||||
|
||||
for key := range haveMap {
|
||||
haveMapKeys[key] = struct{}{}
|
||||
}
|
||||
|
||||
for key, wantMapValue := range wantMap {
|
||||
haveMapValue, ok := haveMap[key]
|
||||
if !ok {
|
||||
// A key in map1 exists, that doesn't exist in map2, we need to update.
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
// Compare the values
|
||||
changesData, err := json.Marshal(wantMapValue)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
completeData, err := json.Marshal(haveMapValue)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(changesData, completeData) {
|
||||
// The values are not equal, we need to update.
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
delete(haveMapKeys, key)
|
||||
|
||||
changeMap[key] = wantMapValue
|
||||
}
|
||||
|
||||
for key := range haveMapKeys {
|
||||
// If there keys in map2 that are not in map1, we have to update.
|
||||
hasChanges = true
|
||||
changeMap[key] = nil
|
||||
}
|
||||
|
||||
return hasChanges, changeMap
|
||||
}
|
||||
|
||||
// synchronize returns a list of operations in order to adjust the "have" list to the "want" list
|
||||
// with taking the available resources on each node into account.
|
||||
func synchronize(wish map[string]string, want []store.Process, have []node.Process, nodes map[string]node.About, nodeRecoverTimeout time.Duration) ([]interface{}, map[string]node.Resources, map[string]string) {
|
||||
resources := NewResourcePlanner(nodes)
|
||||
|
||||
// Mark nodes as throttling where at least one process is still throttling
|
||||
for _, haveP := range have {
|
||||
if haveP.Throttling {
|
||||
resources.Throttling(haveP.NodeID, true)
|
||||
}
|
||||
}
|
||||
|
||||
// A map same as wish, but reflecting the actual situation.
|
||||
reality := map[string]string{}
|
||||
|
||||
// A map from the process ID to the process config of the processes
|
||||
// we want to be running on the nodes.
|
||||
wantMap := map[string]store.Process{}
|
||||
for _, wantP := range want {
|
||||
pid := wantP.Config.ProcessID().String()
|
||||
wantMap[pid] = wantP
|
||||
}
|
||||
|
||||
opStack := []interface{}{}
|
||||
|
||||
// Now we iterate through the processes we actually have running on the nodes
|
||||
// and remove them from the wantMap. We also make sure that they have the correct order.
|
||||
// If a process cannot be found on the wantMap, it will be deleted from the nodes.
|
||||
haveAfterRemove := []node.Process{}
|
||||
wantOrderStart := []node.Process{}
|
||||
|
||||
for _, haveP := range have {
|
||||
pid := haveP.Config.ProcessID().String()
|
||||
wantP, ok := wantMap[pid]
|
||||
if !ok {
|
||||
// The process is not on the wantMap. Delete it and adjust the resources.
|
||||
opStack = append(opStack, processOpDelete{
|
||||
nodeid: haveP.NodeID,
|
||||
processid: haveP.Config.ProcessID(),
|
||||
})
|
||||
|
||||
resources.Remove(haveP.NodeID, haveP.CPU, haveP.Mem)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// The process is on the wantMap. Update the process if the configuration and/or metadata differ.
|
||||
hasConfigChanges := !wantP.Config.Equal(haveP.Config)
|
||||
hasMetadataChanges, metadata := isMetadataUpdateRequired(wantP.Metadata, haveP.Metadata)
|
||||
if hasConfigChanges || hasMetadataChanges {
|
||||
// TODO: When the required resources increase, should we move this process to a node
|
||||
// that has them available? Otherwise, this node might start throttling. However, this
|
||||
// will result in rebalancing.
|
||||
opStack = append(opStack, processOpUpdate{
|
||||
nodeid: haveP.NodeID,
|
||||
processid: haveP.Config.ProcessID(),
|
||||
config: wantP.Config,
|
||||
metadata: metadata,
|
||||
})
|
||||
}
|
||||
|
||||
delete(wantMap, pid)
|
||||
reality[pid] = haveP.NodeID
|
||||
|
||||
if haveP.Order != wantP.Order {
|
||||
if wantP.Order == "start" {
|
||||
// Delay pushing them to the stack in order to have
|
||||
// all resources released first.
|
||||
wantOrderStart = append(wantOrderStart, haveP)
|
||||
} else {
|
||||
opStack = append(opStack, processOpStop{
|
||||
nodeid: haveP.NodeID,
|
||||
processid: haveP.Config.ProcessID(),
|
||||
})
|
||||
|
||||
// Release the resources.
|
||||
resources.Remove(haveP.NodeID, haveP.CPU, haveP.Mem)
|
||||
}
|
||||
}
|
||||
|
||||
haveAfterRemove = append(haveAfterRemove, haveP)
|
||||
}
|
||||
|
||||
for _, haveP := range wantOrderStart {
|
||||
nodeid := haveP.NodeID
|
||||
|
||||
resources.Add(nodeid, haveP.Config.LimitCPU, haveP.Config.LimitMemory)
|
||||
|
||||
// TODO: check if the current node has actually enough resources available,
|
||||
// otherwise it needs to be moved somewhere else. If the node doesn't
|
||||
// have enough resources available, the process will be prevented
|
||||
// from starting.
|
||||
|
||||
/*
|
||||
if hasNodeEnoughResources(r, haveP.Config.LimitCPU, haveP.Config.LimitMemory) {
|
||||
// Consume the resources
|
||||
r.CPU += haveP.Config.LimitCPU
|
||||
r.Mem += haveP.Config.LimitMemory
|
||||
resources[nodeid] = r
|
||||
} else {
|
||||
nodeid = findBestNodeForProcess(resources, haveP.Config.LimitCPU, haveP.Config.LimitMemory)
|
||||
if len(nodeid) == 0 {
|
||||
// Start it anyways and let it run into an error
|
||||
opStack = append(opStack, processOpStart{
|
||||
nodeid: nodeid,
|
||||
processid: haveP.Config.ProcessID(),
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if nodeid != haveP.NodeID {
|
||||
opStack = append(opStack, processOpMove{
|
||||
fromNodeid: haveP.NodeID,
|
||||
toNodeid: nodeid,
|
||||
config: haveP.Config,
|
||||
metadata: haveP.Metadata,
|
||||
order: haveP.Order,
|
||||
})
|
||||
}
|
||||
|
||||
// Consume the resources
|
||||
r, ok := resources[nodeid]
|
||||
if ok {
|
||||
r.CPU += haveP.Config.LimitCPU
|
||||
r.Mem += haveP.Config.LimitMemory
|
||||
resources[nodeid] = r
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
opStack = append(opStack, processOpStart{
|
||||
nodeid: nodeid,
|
||||
processid: haveP.Config.ProcessID(),
|
||||
})
|
||||
}
|
||||
|
||||
have = haveAfterRemove
|
||||
|
||||
// In case a node didn't respond, some PID are still on the wantMap, that would run on
|
||||
// the currently not responding nodes. We use the wish map to assign them to the node.
|
||||
// If the node is unavailable for too long, keep these processes on the wantMap, otherwise
|
||||
// remove them and hope that they will reappear during the nodeRecoverTimeout.
|
||||
for pid := range wantMap {
|
||||
// Check if this PID is be assigned to a node.
|
||||
if nodeid, ok := wish[pid]; ok {
|
||||
// Check for how long the node hasn't been contacted, or if it still exists.
|
||||
if node, ok := nodes[nodeid]; ok {
|
||||
if node.State == "online" {
|
||||
continue
|
||||
}
|
||||
|
||||
if time.Since(node.LastContact) <= nodeRecoverTimeout {
|
||||
reality[pid] = nodeid
|
||||
delete(wantMap, pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The wantMap now contains only those processes that need to be installed on a node.
|
||||
// We will rebuild the "want" array from the wantMap in the same order as the original
|
||||
// "want" array to make the resulting opStack deterministic.
|
||||
wantReduced := []store.Process{}
|
||||
for _, wantP := range want {
|
||||
pid := wantP.Config.ProcessID().String()
|
||||
if _, ok := wantMap[pid]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
wantReduced = append(wantReduced, wantP)
|
||||
}
|
||||
|
||||
// Create a map from the process reference to the node it is running on.
|
||||
haveReferenceAffinity := NewReferenceAffinity(have)
|
||||
|
||||
// Now, all remaining processes in the wantMap must be added to one of the nodes.
|
||||
for _, wantP := range wantReduced {
|
||||
pid := wantP.Config.ProcessID().String()
|
||||
|
||||
// If a process doesn't have any limits defined, reject that process.
|
||||
if wantP.Config.LimitCPU <= 0 || wantP.Config.LimitMemory <= 0 {
|
||||
opStack = append(opStack, processOpReject{
|
||||
processid: wantP.Config.ProcessID(),
|
||||
err: errNoLimitsDefined,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if there are already processes with the same reference, and if so
|
||||
// choose this node. Then check the node if it has enough resources left. If
|
||||
// not, then select a node with the most available resources.
|
||||
nodeid := ""
|
||||
|
||||
// Try to add the process to a node where other processes with the same reference currently reside.
|
||||
raNodes := haveReferenceAffinity.Nodes(wantP.Config.Reference, wantP.Config.Domain)
|
||||
for _, raNodeid := range raNodes {
|
||||
if resources.HasNodeEnough(raNodeid, wantP.Config.LimitCPU, wantP.Config.LimitMemory) {
|
||||
nodeid = raNodeid
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Find the node with the most resources available.
|
||||
if len(nodeid) == 0 {
|
||||
nodes := resources.FindBestNodes(wantP.Config.LimitCPU, wantP.Config.LimitMemory)
|
||||
if len(nodes) > 0 {
|
||||
nodeid = nodes[0]
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodeid) != 0 {
|
||||
opStack = append(opStack, processOpAdd{
|
||||
nodeid: nodeid,
|
||||
config: wantP.Config,
|
||||
metadata: wantP.Metadata,
|
||||
order: wantP.Order,
|
||||
})
|
||||
|
||||
// Consume the resources
|
||||
resources.Add(nodeid, wantP.Config.LimitCPU, wantP.Config.LimitMemory)
|
||||
|
||||
reality[pid] = nodeid
|
||||
|
||||
haveReferenceAffinity.Add(wantP.Config.Reference, wantP.Config.Domain, nodeid)
|
||||
} else {
|
||||
opStack = append(opStack, processOpReject{
|
||||
processid: wantP.Config.ProcessID(),
|
||||
err: errNotEnoughResourcesForDeployment,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return opStack, resources.Map(), reality
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
37
cluster/node.go
Normal file
37
cluster/node.go
Normal file
@ -0,0 +1,37 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster/store"
|
||||
)
|
||||
|
||||
func (c *cluster) ListNodes() map[string]store.Node {
|
||||
return c.store.NodeList()
|
||||
}
|
||||
|
||||
var ErrUnsupportedNodeState = errors.New("unsupported node state")
|
||||
|
||||
func (c *cluster) NodeSetState(origin, id, state string) error {
|
||||
switch state {
|
||||
case "online":
|
||||
case "maintenance":
|
||||
case "leave":
|
||||
default:
|
||||
return ErrUnsupportedNodeState
|
||||
}
|
||||
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.NodeSetState(origin, id, state)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
Operation: store.OpSetNodeState,
|
||||
Data: &store.CommandSetNodeState{
|
||||
NodeID: id,
|
||||
State: state,
|
||||
},
|
||||
}
|
||||
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package proxy
|
||||
package node
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -1,4 +1,4 @@
|
||||
package proxy
|
||||
package node
|
||||
|
||||
import (
|
||||
"testing"
|
||||
806
cluster/node/core.go
Normal file
806
cluster/node/core.go
Normal file
File diff suppressed because it is too large
Load Diff
601
cluster/node/manager.go
Normal file
601
cluster/node/manager.go
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -7,21 +7,9 @@ import (
|
||||
"github.com/datarhei/core/v16/restream/app"
|
||||
)
|
||||
|
||||
func (c *cluster) ListProcesses() []store.Process {
|
||||
return c.store.ListProcesses()
|
||||
}
|
||||
|
||||
func (c *cluster) GetProcess(id app.ProcessID) (store.Process, error) {
|
||||
return c.store.GetProcess(id)
|
||||
}
|
||||
|
||||
func (c *cluster) AddProcess(origin string, config *app.Config) error {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) ProcessAdd(origin string, config *app.Config) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.AddProcess(origin, config)
|
||||
return c.forwarder.ProcessAdd(origin, config)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -34,13 +22,9 @@ func (c *cluster) AddProcess(origin string, config *app.Config) error {
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) RemoveProcess(origin string, id app.ProcessID) error {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) ProcessRemove(origin string, id app.ProcessID) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.RemoveProcess(origin, id)
|
||||
return c.forwarder.ProcessRemove(origin, id)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -53,13 +37,9 @@ func (c *cluster) RemoveProcess(origin string, id app.ProcessID) error {
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) UpdateProcess(origin string, id app.ProcessID, config *app.Config) error {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) ProcessUpdate(origin string, id app.ProcessID, config *app.Config) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.UpdateProcess(origin, id, config)
|
||||
return c.forwarder.ProcessUpdate(origin, id, config)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -73,14 +53,10 @@ func (c *cluster) UpdateProcess(origin string, id app.ProcessID, config *app.Con
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) SetProcessCommand(origin string, id app.ProcessID, command string) error {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return ErrDegraded
|
||||
}
|
||||
|
||||
func (c *cluster) ProcessSetCommand(origin string, id app.ProcessID, command string) error {
|
||||
if command == "start" || command == "stop" {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.SetProcessCommand(origin, id, command)
|
||||
return c.forwarder.ProcessSetCommand(origin, id, command)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -94,21 +70,32 @@ func (c *cluster) SetProcessCommand(origin string, id app.ProcessID, command str
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
nodeid, err := c.proxy.FindNodeFromProcess(id)
|
||||
nodeid, err := c.manager.ProcessFindNodeID(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the process '%s' is not registered with any node: %w", id.String(), err)
|
||||
}
|
||||
|
||||
return c.proxy.CommandProcess(nodeid, id, command)
|
||||
return c.manager.ProcessCommand(nodeid, id, command)
|
||||
}
|
||||
|
||||
func (c *cluster) SetProcessMetadata(origin string, id app.ProcessID, key string, data interface{}) error {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return ErrDegraded
|
||||
func (c *cluster) ProcessesRelocate(origin string, relocations map[app.ProcessID]string) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.ProcessesRelocate(origin, relocations)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
Operation: store.OpSetRelocateProcess,
|
||||
Data: &store.CommandSetRelocateProcess{
|
||||
Map: relocations,
|
||||
},
|
||||
}
|
||||
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) ProcessSetMetadata(origin string, id app.ProcessID, key string, data interface{}) error {
|
||||
if !c.IsRaftLeader() {
|
||||
return c.forwarder.SetProcessMetadata(origin, id, key, data)
|
||||
return c.forwarder.ProcessSetMetadata(origin, id, key, data)
|
||||
}
|
||||
|
||||
cmd := &store.Command{
|
||||
@ -123,12 +110,8 @@ func (c *cluster) SetProcessMetadata(origin string, id app.ProcessID, key string
|
||||
return c.applyCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *cluster) GetProcessMetadata(origin string, id app.ProcessID, key string) (interface{}, error) {
|
||||
if ok, _ := c.IsDegraded(); ok {
|
||||
return nil, ErrDegraded
|
||||
}
|
||||
|
||||
p, err := c.store.GetProcess(id)
|
||||
func (c *cluster) ProcessGetMetadata(origin string, id app.ProcessID, key string) (interface{}, error) {
|
||||
p, err := c.store.ProcessGet(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -144,7 +127,3 @@ func (c *cluster) GetProcessMetadata(origin string, id app.ProcessID, key string
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *cluster) GetProcessNodeMap() map[string]string {
|
||||
return c.store.GetProcessNodeMap()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
123
cluster/resources.go
Normal file
123
cluster/resources.go
Normal file
@ -0,0 +1,123 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
)
|
||||
|
||||
type resourcePlanner struct {
|
||||
nodes map[string]node.Resources
|
||||
blocked map[string]struct{}
|
||||
}
|
||||
|
||||
func NewResourcePlanner(nodes map[string]node.About) *resourcePlanner {
|
||||
r := &resourcePlanner{
|
||||
nodes: map[string]node.Resources{},
|
||||
blocked: map[string]struct{}{},
|
||||
}
|
||||
|
||||
for nodeid, about := range nodes {
|
||||
r.nodes[nodeid] = about.Resources
|
||||
if about.State != "online" {
|
||||
r.blocked[nodeid] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *resourcePlanner) Throttling(nodeid string, throttling bool) {
|
||||
res, hasNode := r.nodes[nodeid]
|
||||
if !hasNode {
|
||||
return
|
||||
}
|
||||
|
||||
res.IsThrottling = throttling
|
||||
|
||||
r.nodes[nodeid] = res
|
||||
}
|
||||
|
||||
// HasNodeEnough returns whether a node has enough resources available for the
|
||||
// requested cpu and memory consumption.
|
||||
func (r *resourcePlanner) HasNodeEnough(nodeid string, cpu float64, mem uint64) bool {
|
||||
res, hasNode := r.nodes[nodeid]
|
||||
if !hasNode {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, hasNode := r.blocked[nodeid]; hasNode {
|
||||
return false
|
||||
}
|
||||
|
||||
if res.Error == nil && res.CPU+cpu < res.CPULimit && res.Mem+mem < res.MemLimit && !res.IsThrottling {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// FindBestNodes returns an array of nodeids that can fit the requested cpu and memory requirements. If no
|
||||
// such node is available, an empty array is returned. The array is sorted by the most suitable node first.
|
||||
func (r *resourcePlanner) FindBestNodes(cpu float64, mem uint64) []string {
|
||||
nodes := []string{}
|
||||
|
||||
for id := range r.nodes {
|
||||
if r.HasNodeEnough(id, cpu, mem) {
|
||||
nodes = append(nodes, id)
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(nodes, func(i, j int) bool {
|
||||
nodeA, nodeB := nodes[i], nodes[j]
|
||||
|
||||
if r.nodes[nodeA].CPU != r.nodes[nodeB].CPU {
|
||||
return r.nodes[nodeA].CPU < r.nodes[nodeB].CPU
|
||||
}
|
||||
|
||||
return r.nodes[nodeA].Mem <= r.nodes[nodeB].Mem
|
||||
})
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
// Add adds the resources of the node according to the cpu and memory utilization.
|
||||
func (r *resourcePlanner) Add(nodeid string, cpu float64, mem uint64) {
|
||||
res, hasRes := r.nodes[nodeid]
|
||||
if !hasRes {
|
||||
return
|
||||
}
|
||||
|
||||
res.CPU += cpu
|
||||
res.Mem += mem
|
||||
r.nodes[nodeid] = res
|
||||
}
|
||||
|
||||
// Remove subtracts the resources from the node according to the cpu and memory utilization.
|
||||
func (r *resourcePlanner) Remove(nodeid string, cpu float64, mem uint64) {
|
||||
res, hasRes := r.nodes[nodeid]
|
||||
if !hasRes {
|
||||
return
|
||||
}
|
||||
|
||||
res.CPU -= cpu
|
||||
if res.CPU < 0 {
|
||||
res.CPU = 0
|
||||
}
|
||||
if mem >= res.Mem {
|
||||
res.Mem = 0
|
||||
} else {
|
||||
res.Mem -= mem
|
||||
}
|
||||
r.nodes[nodeid] = res
|
||||
}
|
||||
|
||||
// Move adjusts the resources from the target and source node according to the cpu and memory utilization.
|
||||
func (r *resourcePlanner) Move(target, source string, cpu float64, mem uint64) {
|
||||
r.Add(target, cpu, mem)
|
||||
r.Remove(source, cpu, mem)
|
||||
}
|
||||
|
||||
func (r *resourcePlanner) Map() map[string]node.Resources {
|
||||
return r.nodes
|
||||
}
|
||||
6
cluster/store/errors.go
Normal file
6
cluster/store/errors.go
Normal file
@ -0,0 +1,6 @@
|
||||
package store
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrNotFound = errors.New("")
|
||||
var ErrBadRequest = errors.New("")
|
||||
@ -11,7 +11,7 @@ func (s *store) addIdentity(cmd CommandAddIdentity) error {
|
||||
|
||||
err := s.data.Users.userlist.Add(cmd.Identity)
|
||||
if err != nil {
|
||||
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%w", cmd.Identity.Name, ErrBadRequest)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@ -30,17 +30,17 @@ func (s *store) updateIdentity(cmd CommandUpdateIdentity) error {
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if cmd.Name == "$anon" {
|
||||
return fmt.Errorf("the identity with the name '%s' can't be updated", cmd.Name)
|
||||
return fmt.Errorf("the identity with the name '%s' can't be updated%w", cmd.Name, ErrBadRequest)
|
||||
}
|
||||
|
||||
oldUser, err := s.data.Users.userlist.Get(cmd.Name)
|
||||
if err != nil {
|
||||
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%w", cmd.Name, ErrNotFound)
|
||||
}
|
||||
|
||||
o, ok := s.data.Users.Users[oldUser.Name]
|
||||
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%w", cmd.Name, ErrNotFound)
|
||||
}
|
||||
|
||||
err = s.data.Users.userlist.Update(cmd.Name, cmd.Identity)
|
||||
@ -50,7 +50,7 @@ func (s *store) updateIdentity(cmd CommandUpdateIdentity) error {
|
||||
|
||||
user, err := s.data.Users.userlist.Get(cmd.Identity.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the identity with the name '%s' doesn't exist", cmd.Identity.Name)
|
||||
return fmt.Errorf("the identity with the name '%s' doesn't exist%w", cmd.Identity.Name, ErrNotFound)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@ -89,7 +89,7 @@ func (s *store) removeIdentity(cmd CommandRemoveIdentity) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) ListUsers() Users {
|
||||
func (s *store) IAMIdentityList() Users {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@ -104,7 +104,7 @@ func (s *store) ListUsers() Users {
|
||||
return u
|
||||
}
|
||||
|
||||
func (s *store) GetUser(name string) Users {
|
||||
func (s *store) IAMIdentityGet(name string) Users {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
|
||||
@ -49,7 +49,7 @@ func TestAddIdentity(t *testing.T) {
|
||||
require.Equal(t, 1, len(s.data.Users.Users))
|
||||
require.Equal(t, 0, len(s.data.Policies.Policies))
|
||||
|
||||
u := s.GetUser("foobar")
|
||||
u := s.IAMIdentityGet("foobar")
|
||||
require.Equal(t, 1, len(u.Users))
|
||||
|
||||
user := u.Users[0]
|
||||
@ -254,7 +254,7 @@ func TestUpdateIdentity(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(s.data.Users.Users))
|
||||
|
||||
foobar := s.GetUser("foobar1").Users[0]
|
||||
foobar := s.IAMIdentityGet("foobar1").Users[0]
|
||||
require.True(t, foobar.CreatedAt.Equal(foobar.UpdatedAt))
|
||||
require.False(t, time.Time{}.Equal(foobar.CreatedAt))
|
||||
|
||||
@ -287,13 +287,13 @@ func TestUpdateIdentity(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(s.data.Users.Users))
|
||||
|
||||
u := s.GetUser("foobar1")
|
||||
u := s.IAMIdentityGet("foobar1")
|
||||
require.Empty(t, u.Users)
|
||||
|
||||
u = s.GetUser("foobar2")
|
||||
u = s.IAMIdentityGet("foobar2")
|
||||
require.NotEmpty(t, u.Users)
|
||||
|
||||
u = s.GetUser("foobaz")
|
||||
u = s.IAMIdentityGet("foobaz")
|
||||
require.NotEmpty(t, u.Users)
|
||||
|
||||
require.True(t, u.Users[0].CreatedAt.Equal(foobar.CreatedAt))
|
||||
@ -367,22 +367,22 @@ func TestUpdateIdentityWithAlias(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(s.data.Users.Users))
|
||||
|
||||
u := s.GetUser("foobar1")
|
||||
u := s.IAMIdentityGet("foobar1")
|
||||
require.Empty(t, u.Users)
|
||||
|
||||
u = s.GetUser("fooalias1")
|
||||
u = s.IAMIdentityGet("fooalias1")
|
||||
require.Empty(t, u.Users)
|
||||
|
||||
u = s.GetUser("foobar2")
|
||||
u = s.IAMIdentityGet("foobar2")
|
||||
require.NotEmpty(t, u.Users)
|
||||
|
||||
u = s.GetUser("fooalias2")
|
||||
u = s.IAMIdentityGet("fooalias2")
|
||||
require.NotEmpty(t, u.Users)
|
||||
|
||||
u = s.GetUser("foobaz")
|
||||
u = s.IAMIdentityGet("foobaz")
|
||||
require.NotEmpty(t, u.Users)
|
||||
|
||||
u = s.GetUser("fooalias")
|
||||
u = s.IAMIdentityGet("fooalias")
|
||||
require.NotEmpty(t, u.Users)
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"strings"
|
||||
"time"
|
||||
@ -25,7 +26,7 @@ func (s *store) unsetKV(cmd CommandUnsetKV) error {
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if _, ok := s.data.KVS[cmd.Key]; !ok {
|
||||
return fs.ErrNotExist
|
||||
return errors.Join(fs.ErrNotExist, ErrNotFound)
|
||||
}
|
||||
|
||||
delete(s.data.KVS, cmd.Key)
|
||||
@ -33,7 +34,7 @@ func (s *store) unsetKV(cmd CommandUnsetKV) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) ListKVS(prefix string) map[string]Value {
|
||||
func (s *store) KVSList(prefix string) map[string]Value {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@ -50,13 +51,13 @@ func (s *store) ListKVS(prefix string) map[string]Value {
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *store) GetFromKVS(key string) (Value, error) {
|
||||
func (s *store) KVSGetValue(key string) (Value, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
value, ok := s.data.KVS[key]
|
||||
if !ok {
|
||||
return Value{}, fs.ErrNotExist
|
||||
return Value{}, errors.Join(fs.ErrNotExist, ErrNotFound)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
|
||||
@ -34,7 +34,7 @@ func TestSetKV(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
value, err := s.GetFromKVS("foo")
|
||||
value, err := s.KVSGetValue("foo")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "bar", value.Value)
|
||||
|
||||
@ -46,7 +46,7 @@ func TestSetKV(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
value, err = s.GetFromKVS("foo")
|
||||
value, err = s.KVSGetValue("foo")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "baz", value.Value)
|
||||
require.Greater(t, value.UpdatedAt, updatedAt)
|
||||
@ -86,7 +86,7 @@ func TestUnsetKVCommand(t *testing.T) {
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Equal(t, fs.ErrNotExist, err)
|
||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||
}
|
||||
|
||||
func TestUnsetKV(t *testing.T) {
|
||||
@ -99,7 +99,7 @@ func TestUnsetKV(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetFromKVS("foo")
|
||||
_, err = s.KVSGetValue("foo")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.unsetKV(CommandUnsetKV{
|
||||
@ -107,7 +107,7 @@ func TestUnsetKV(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetFromKVS("foo")
|
||||
_, err = s.KVSGetValue("foo")
|
||||
require.Error(t, err)
|
||||
require.Equal(t, fs.ErrNotExist, err)
|
||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ func (s *store) createLock(cmd CommandCreateLock) error {
|
||||
|
||||
if ok {
|
||||
if time.Now().Before(validUntil) {
|
||||
return fmt.Errorf("the lock with the ID '%s' already exists", cmd.Name)
|
||||
return fmt.Errorf("the lock with the ID '%s' already exists%w", cmd.Name, ErrBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ func (s *store) deleteLock(cmd CommandDeleteLock) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) clearLocks(cmd CommandClearLocks) error {
|
||||
func (s *store) clearLocks(_ CommandClearLocks) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@ -51,7 +51,7 @@ func (s *store) clearLocks(cmd CommandClearLocks) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) HasLock(name string) bool {
|
||||
func (s *store) LockHasLock(name string) bool {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@ -60,7 +60,7 @@ func (s *store) HasLock(name string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *store) ListLocks() map[string]time.Time {
|
||||
func (s *store) LockList() map[string]time.Time {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
|
||||
35
cluster/store/node.go
Normal file
35
cluster/store/node.go
Normal file
@ -0,0 +1,35 @@
|
||||
package store
|
||||
|
||||
import "time"
|
||||
|
||||
func (s *store) setNodeState(cmd CommandSetNodeState) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if cmd.State == "online" {
|
||||
delete(s.data.Nodes, cmd.NodeID)
|
||||
return nil
|
||||
}
|
||||
|
||||
node := s.data.Nodes[cmd.NodeID]
|
||||
|
||||
node.State = cmd.State
|
||||
node.UpdatedAt = time.Now()
|
||||
|
||||
s.data.Nodes[cmd.NodeID] = node
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) NodeList() map[string]Node {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
m := map[string]Node{}
|
||||
|
||||
for id, node := range s.data.Nodes {
|
||||
m[id] = node
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
@ -16,12 +16,12 @@ func (s *store) setPolicies(cmd CommandSetPolicies) error {
|
||||
if cmd.Name != "$anon" {
|
||||
user, err := s.data.Users.userlist.Get(cmd.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the identity with the name '%s' doesn't exist", cmd.Name)
|
||||
return fmt.Errorf("unknown identity %s%w", cmd.Name, ErrNotFound)
|
||||
}
|
||||
|
||||
u, ok := s.data.Users.Users[user.Name]
|
||||
if !ok {
|
||||
return fmt.Errorf("the identity with the name '%s' doesn't exist", cmd.Name)
|
||||
return fmt.Errorf("unknown identity %s%w", cmd.Name, ErrNotFound)
|
||||
}
|
||||
|
||||
u.UpdatedAt = now
|
||||
@ -45,7 +45,7 @@ func (s *store) setPolicies(cmd CommandSetPolicies) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) ListPolicies() Policies {
|
||||
func (s *store) IAMPolicyList() Policies {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@ -60,7 +60,7 @@ func (s *store) ListPolicies() Policies {
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *store) ListUserPolicies(name string) Policies {
|
||||
func (s *store) IAMIdentityPolicyList(name string) Policies {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
|
||||
@ -88,7 +88,7 @@ func TestSetPolicies(t *testing.T) {
|
||||
require.Equal(t, 1, len(s.data.Users.Users))
|
||||
require.Equal(t, 0, len(s.data.Policies.Policies))
|
||||
|
||||
users := s.GetUser("foobar")
|
||||
users := s.IAMIdentityGet("foobar")
|
||||
require.NotEmpty(t, users.Users)
|
||||
|
||||
updatedAt := users.Users[0].UpdatedAt
|
||||
@ -101,7 +101,7 @@ func TestSetPolicies(t *testing.T) {
|
||||
require.Equal(t, 1, len(s.data.Users.Users))
|
||||
require.Equal(t, 2, len(s.data.Policies.Policies["foobar"]))
|
||||
|
||||
users = s.GetUser("foobar")
|
||||
users = s.IAMIdentityGet("foobar")
|
||||
require.NotEmpty(t, users.Users)
|
||||
|
||||
require.False(t, updatedAt.Equal(users.Users[0].UpdatedAt))
|
||||
|
||||
@ -14,12 +14,12 @@ func (s *store) addProcess(cmd CommandAddProcess) error {
|
||||
id := cmd.Config.ProcessID().String()
|
||||
|
||||
if cmd.Config.LimitCPU <= 0 || cmd.Config.LimitMemory <= 0 {
|
||||
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%w", id, ErrBadRequest)
|
||||
}
|
||||
|
||||
_, ok := s.data.Process[id]
|
||||
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%w", id, ErrBadRequest)
|
||||
}
|
||||
|
||||
order := "stop"
|
||||
@ -48,7 +48,7 @@ func (s *store) removeProcess(cmd CommandRemoveProcess) error {
|
||||
|
||||
_, ok := s.data.Process[id]
|
||||
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%w", id, ErrNotFound)
|
||||
}
|
||||
|
||||
delete(s.data.Process, id)
|
||||
@ -64,12 +64,12 @@ func (s *store) updateProcess(cmd CommandUpdateProcess) error {
|
||||
dstid := cmd.Config.ProcessID().String()
|
||||
|
||||
if cmd.Config.LimitCPU <= 0 || cmd.Config.LimitMemory <= 0 {
|
||||
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%w", dstid, ErrBadRequest)
|
||||
}
|
||||
|
||||
p, ok := s.data.Process[srcid]
|
||||
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%w", srcid, ErrNotFound)
|
||||
}
|
||||
|
||||
if p.Config.Equal(cmd.Config) {
|
||||
@ -87,7 +87,7 @@ func (s *store) updateProcess(cmd CommandUpdateProcess) error {
|
||||
|
||||
_, ok = s.data.Process[dstid]
|
||||
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%w", dstid, ErrBadRequest)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@ -102,6 +102,30 @@ func (s *store) updateProcess(cmd CommandUpdateProcess) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) setRelocateProcess(cmd CommandSetRelocateProcess) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
for processid, targetNodeid := range cmd.Map {
|
||||
id := processid.String()
|
||||
s.data.ProcessRelocateMap[id] = targetNodeid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) unsetRelocateProcess(cmd CommandUnsetRelocateProcess) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
for _, processid := range cmd.ID {
|
||||
id := processid.String()
|
||||
delete(s.data.ProcessRelocateMap, id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) setProcessOrder(cmd CommandSetProcessOrder) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
@ -110,7 +134,7 @@ func (s *store) setProcessOrder(cmd CommandSetProcessOrder) error {
|
||||
|
||||
p, ok := s.data.Process[id]
|
||||
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%w", cmd.ID, ErrNotFound)
|
||||
}
|
||||
|
||||
p.Order = cmd.Order
|
||||
@ -129,7 +153,7 @@ func (s *store) setProcessMetadata(cmd CommandSetProcessMetadata) error {
|
||||
|
||||
p, ok := s.data.Process[id]
|
||||
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%w", cmd.ID, ErrNotFound)
|
||||
}
|
||||
|
||||
if p.Metadata == nil {
|
||||
@ -156,7 +180,7 @@ func (s *store) setProcessError(cmd CommandSetProcessError) error {
|
||||
|
||||
p, ok := s.data.Process[id]
|
||||
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%w", cmd.ID, ErrNotFound)
|
||||
}
|
||||
|
||||
p.Error = cmd.Error
|
||||
@ -175,7 +199,7 @@ func (s *store) setProcessNodeMap(cmd CommandSetProcessNodeMap) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) ListProcesses() []Process {
|
||||
func (s *store) ProcessList() []Process {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@ -195,13 +219,13 @@ func (s *store) ListProcesses() []Process {
|
||||
return processes
|
||||
}
|
||||
|
||||
func (s *store) GetProcess(id app.ProcessID) (Process, error) {
|
||||
func (s *store) ProcessGet(id app.ProcessID) (Process, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
process, ok := s.data.Process[id.String()]
|
||||
if !ok {
|
||||
return Process{}, fmt.Errorf("not found")
|
||||
return Process{}, fmt.Errorf("not found%w", ErrNotFound)
|
||||
}
|
||||
|
||||
return Process{
|
||||
@ -214,7 +238,7 @@ func (s *store) GetProcess(id app.ProcessID) (Process, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *store) GetProcessNodeMap() map[string]string {
|
||||
func (s *store) ProcessGetNodeMap() map[string]string {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@ -226,3 +250,16 @@ func (s *store) GetProcessNodeMap() map[string]string {
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *store) ProcessGetRelocateMap() map[string]string {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
m := map[string]string{}
|
||||
|
||||
for key, value := range s.data.ProcessRelocateMap {
|
||||
m[key] = value
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
@ -301,13 +301,13 @@ func TestUpdateProcess(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(s.data.Process))
|
||||
|
||||
_, err = s.GetProcess(config1.ProcessID())
|
||||
_, err = s.ProcessGet(config1.ProcessID())
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = s.GetProcess(config2.ProcessID())
|
||||
_, err = s.ProcessGet(config2.ProcessID())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetProcess(config.ProcessID())
|
||||
_, err = s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@ -330,7 +330,7 @@ func TestSetProcessOrderCommand(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, s.data.Process)
|
||||
|
||||
p, err := s.GetProcess(config.ProcessID())
|
||||
p, err := s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "stop", p.Order)
|
||||
|
||||
@ -343,7 +343,7 @@ func TestSetProcessOrderCommand(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err = s.GetProcess(config.ProcessID())
|
||||
p, err = s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "start", p.Order)
|
||||
}
|
||||
@ -382,7 +382,7 @@ func TestSetProcessOrder(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err := s.GetProcess(config.ProcessID())
|
||||
p, err := s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "stop", p.Order)
|
||||
|
||||
@ -392,7 +392,7 @@ func TestSetProcessOrder(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err = s.GetProcess(config.ProcessID())
|
||||
p, err = s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "start", p.Order)
|
||||
}
|
||||
@ -416,7 +416,7 @@ func TestSetProcessMetadataCommand(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, s.data.Process)
|
||||
|
||||
p, err := s.GetProcess(config.ProcessID())
|
||||
p, err := s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, p.Metadata)
|
||||
|
||||
@ -432,7 +432,7 @@ func TestSetProcessMetadataCommand(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err = s.GetProcess(config.ProcessID())
|
||||
p, err = s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEmpty(t, p.Metadata)
|
||||
@ -477,7 +477,7 @@ func TestSetProcessMetadata(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err := s.GetProcess(config.ProcessID())
|
||||
p, err := s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEmpty(t, p.Metadata)
|
||||
@ -492,7 +492,7 @@ func TestSetProcessMetadata(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err = s.GetProcess(config.ProcessID())
|
||||
p, err = s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEmpty(t, p.Metadata)
|
||||
@ -506,7 +506,7 @@ func TestSetProcessMetadata(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err = s.GetProcess(config.ProcessID())
|
||||
p, err = s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEmpty(t, p.Metadata)
|
||||
@ -533,7 +533,7 @@ func TestSetProcessErrorCommand(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, s.data.Process)
|
||||
|
||||
p, err := s.GetProcess(config.ProcessID())
|
||||
p, err := s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", p.Error)
|
||||
|
||||
@ -546,7 +546,7 @@ func TestSetProcessErrorCommand(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err = s.GetProcess(config.ProcessID())
|
||||
p, err = s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobar", p.Error)
|
||||
}
|
||||
@ -585,7 +585,7 @@ func TestSetProcessError(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err := s.GetProcess(config.ProcessID())
|
||||
p, err := s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", p.Error)
|
||||
|
||||
@ -595,7 +595,7 @@ func TestSetProcessError(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err = s.GetProcess(config.ProcessID())
|
||||
p, err = s.ProcessGet(config.ProcessID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobar", p.Error)
|
||||
}
|
||||
@ -642,6 +642,6 @@ func TestSetProcessNodeMap(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, m2, s.data.ProcessNodeMap)
|
||||
|
||||
m := s.GetProcessNodeMap()
|
||||
m := s.ProcessGetNodeMap()
|
||||
require.Equal(t, m2, m)
|
||||
}
|
||||
|
||||
@ -20,20 +20,23 @@ type Store interface {
|
||||
|
||||
OnApply(func(op Operation))
|
||||
|
||||
ListProcesses() []Process
|
||||
GetProcess(id app.ProcessID) (Process, error)
|
||||
GetProcessNodeMap() map[string]string
|
||||
ProcessList() []Process
|
||||
ProcessGet(id app.ProcessID) (Process, error)
|
||||
ProcessGetNodeMap() map[string]string
|
||||
ProcessGetRelocateMap() map[string]string
|
||||
|
||||
ListUsers() Users
|
||||
GetUser(name string) Users
|
||||
ListPolicies() Policies
|
||||
ListUserPolicies(name string) Policies
|
||||
IAMIdentityList() Users
|
||||
IAMIdentityGet(name string) Users
|
||||
IAMIdentityPolicyList(name string) Policies
|
||||
IAMPolicyList() Policies
|
||||
|
||||
HasLock(name string) bool
|
||||
ListLocks() map[string]time.Time
|
||||
LockHasLock(name string) bool
|
||||
LockList() map[string]time.Time
|
||||
|
||||
ListKVS(prefix string) map[string]Value
|
||||
GetFromKVS(key string) (Value, error)
|
||||
KVSList(prefix string) map[string]Value
|
||||
KVSGetValue(key string) (Value, error)
|
||||
|
||||
NodeList() map[string]Node
|
||||
}
|
||||
|
||||
type Process struct {
|
||||
@ -60,25 +63,33 @@ type Value struct {
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
State string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type Operation string
|
||||
|
||||
const (
|
||||
OpAddProcess Operation = "addProcess"
|
||||
OpRemoveProcess Operation = "removeProcess"
|
||||
OpUpdateProcess Operation = "updateProcess"
|
||||
OpSetProcessOrder Operation = "setProcessOrder"
|
||||
OpSetProcessMetadata Operation = "setProcessMetadata"
|
||||
OpSetProcessError Operation = "setProcessError"
|
||||
OpAddIdentity Operation = "addIdentity"
|
||||
OpUpdateIdentity Operation = "updateIdentity"
|
||||
OpRemoveIdentity Operation = "removeIdentity"
|
||||
OpSetPolicies Operation = "setPolicies"
|
||||
OpSetProcessNodeMap Operation = "setProcessNodeMap"
|
||||
OpCreateLock Operation = "createLock"
|
||||
OpDeleteLock Operation = "deleteLock"
|
||||
OpClearLocks Operation = "clearLocks"
|
||||
OpSetKV Operation = "setKV"
|
||||
OpUnsetKV Operation = "unsetKV"
|
||||
OpAddProcess Operation = "addProcess"
|
||||
OpRemoveProcess Operation = "removeProcess"
|
||||
OpUpdateProcess Operation = "updateProcess"
|
||||
OpSetRelocateProcess Operation = "setRelocateProcess"
|
||||
OpUnsetRelocateProcess Operation = "unsetRelocateProcess"
|
||||
OpSetProcessOrder Operation = "setProcessOrder"
|
||||
OpSetProcessMetadata Operation = "setProcessMetadata"
|
||||
OpSetProcessError Operation = "setProcessError"
|
||||
OpAddIdentity Operation = "addIdentity"
|
||||
OpUpdateIdentity Operation = "updateIdentity"
|
||||
OpRemoveIdentity Operation = "removeIdentity"
|
||||
OpSetPolicies Operation = "setPolicies"
|
||||
OpSetProcessNodeMap Operation = "setProcessNodeMap"
|
||||
OpCreateLock Operation = "createLock"
|
||||
OpDeleteLock Operation = "deleteLock"
|
||||
OpClearLocks Operation = "clearLocks"
|
||||
OpSetKV Operation = "setKV"
|
||||
OpUnsetKV Operation = "unsetKV"
|
||||
OpSetNodeState Operation = "setNodeState"
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
@ -90,13 +101,21 @@ type CommandAddProcess struct {
|
||||
Config *app.Config
|
||||
}
|
||||
|
||||
type CommandRemoveProcess struct {
|
||||
ID app.ProcessID
|
||||
}
|
||||
|
||||
type CommandUpdateProcess struct {
|
||||
ID app.ProcessID
|
||||
Config *app.Config
|
||||
}
|
||||
|
||||
type CommandRemoveProcess struct {
|
||||
ID app.ProcessID
|
||||
type CommandSetRelocateProcess struct {
|
||||
Map map[app.ProcessID]string
|
||||
}
|
||||
|
||||
type CommandUnsetRelocateProcess struct {
|
||||
ID []app.ProcessID
|
||||
}
|
||||
|
||||
type CommandSetProcessOrder struct {
|
||||
@ -115,6 +134,10 @@ type CommandSetProcessError struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
type CommandSetProcessNodeMap struct {
|
||||
Map map[string]string
|
||||
}
|
||||
|
||||
type CommandAddIdentity struct {
|
||||
Identity identity.User
|
||||
}
|
||||
@ -133,10 +156,6 @@ type CommandSetPolicies struct {
|
||||
Policies []access.Policy
|
||||
}
|
||||
|
||||
type CommandSetProcessNodeMap struct {
|
||||
Map map[string]string
|
||||
}
|
||||
|
||||
type CommandCreateLock struct {
|
||||
Name string
|
||||
ValidUntil time.Time
|
||||
@ -157,10 +176,16 @@ type CommandUnsetKV struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
type CommandSetNodeState struct {
|
||||
NodeID string
|
||||
State string
|
||||
}
|
||||
|
||||
type storeData struct {
|
||||
Version uint64
|
||||
Process map[string]Process
|
||||
ProcessNodeMap map[string]string
|
||||
Version uint64
|
||||
Process map[string]Process // processid -> process
|
||||
ProcessNodeMap map[string]string // processid -> nodeid
|
||||
ProcessRelocateMap map[string]string // processid -> nodeid
|
||||
|
||||
Users struct {
|
||||
UpdatedAt time.Time
|
||||
@ -176,6 +201,8 @@ type storeData struct {
|
||||
Locks map[string]time.Time
|
||||
|
||||
KVS map[string]Value
|
||||
|
||||
Nodes map[string]Node
|
||||
}
|
||||
|
||||
func (s *storeData) init() {
|
||||
@ -184,6 +211,7 @@ func (s *storeData) init() {
|
||||
s.Version = 1
|
||||
s.Process = map[string]Process{}
|
||||
s.ProcessNodeMap = map[string]string{}
|
||||
s.ProcessRelocateMap = map[string]string{}
|
||||
s.Users.UpdatedAt = now
|
||||
s.Users.Users = map[string]identity.User{}
|
||||
s.Users.userlist = identity.NewUserList()
|
||||
@ -191,6 +219,7 @@ func (s *storeData) init() {
|
||||
s.Policies.Policies = map[string][]access.Policy{}
|
||||
s.Locks = map[string]time.Time{}
|
||||
s.KVS = map[string]Value{}
|
||||
s.Nodes = map[string]Node{}
|
||||
}
|
||||
|
||||
// store implements a raft.FSM
|
||||
@ -297,6 +326,22 @@ func (s *store) applyCommand(c Command) error {
|
||||
}
|
||||
|
||||
err = s.updateProcess(cmd)
|
||||
case OpSetRelocateProcess:
|
||||
cmd := CommandSetRelocateProcess{}
|
||||
err = decodeCommand(&cmd, c.Data)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
err = s.setRelocateProcess(cmd)
|
||||
case OpUnsetRelocateProcess:
|
||||
cmd := CommandUnsetRelocateProcess{}
|
||||
err = decodeCommand(&cmd, c.Data)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
err = s.unsetRelocateProcess(cmd)
|
||||
case OpSetProcessOrder:
|
||||
cmd := CommandSetProcessOrder{}
|
||||
err = decodeCommand(&cmd, c.Data)
|
||||
@ -401,6 +446,14 @@ func (s *store) applyCommand(c Command) error {
|
||||
}
|
||||
|
||||
err = s.unsetKV(cmd)
|
||||
case OpSetNodeState:
|
||||
cmd := CommandSetNodeState{}
|
||||
err = decodeCommand(&cmd, c.Data)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
err = s.setNodeState(cmd)
|
||||
default:
|
||||
s.logger.Warn().WithField("operation", c.Operation).Log("Unknown operation")
|
||||
err = fmt.Errorf("unknown operation: %s", c.Operation)
|
||||
@ -448,6 +501,14 @@ func (s *store) Restore(snapshot io.ReadCloser) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if data.ProcessNodeMap == nil {
|
||||
data.ProcessNodeMap = map[string]string{}
|
||||
}
|
||||
|
||||
if data.ProcessRelocateMap == nil {
|
||||
data.ProcessRelocateMap = map[string]string{}
|
||||
}
|
||||
|
||||
for id, p := range data.Process {
|
||||
if p.Metadata != nil {
|
||||
continue
|
||||
|
||||
@ -38,6 +38,6 @@ func ParseClusterVersion(version string) (ClusterVersion, error) {
|
||||
// Version of the cluster
|
||||
var Version = ClusterVersion{
|
||||
Major: 2,
|
||||
Minor: 0,
|
||||
Minor: 1,
|
||||
Patch: 0,
|
||||
}
|
||||
|
||||
228
docs/docs.go
228
docs/docs.go
@ -209,6 +209,35 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/db/node": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "List of nodes in the cluster DB",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v16.?.?"
|
||||
],
|
||||
"summary": "List nodes in the cluster DB",
|
||||
"operationId": "cluster-3-db-list-nodes",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.ClusterStoreNode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/db/policies": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -1381,6 +1410,107 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/node/{id}/state": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get the state of a node with the given ID",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v16.?.?"
|
||||
],
|
||||
"summary": "Get the state of a node with the given ID",
|
||||
"operationId": "cluster-3-get-node-state",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Node ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.ClusterNodeState"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Set the state of a node with the given ID",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v16.?.?"
|
||||
],
|
||||
"summary": "Set the state of a node with the given ID",
|
||||
"operationId": "cluster-3-set-node-state",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Node ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "State",
|
||||
"name": "config",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.ClusterNodeState"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/node/{id}/version": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -2039,6 +2169,49 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/reallocation": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Retrieve snapshot of the cluster DB",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v16.?.?"
|
||||
],
|
||||
"summary": "Retrieve snapshot of the cluster DB",
|
||||
"operationId": "cluster-3-reallocation",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Process reallocations",
|
||||
"name": "reallocations",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.ClusterProcessReallocate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/snapshot": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -5092,12 +5265,42 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ClusterNodeState": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"state"
|
||||
],
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"online",
|
||||
"maintenance",
|
||||
"leave"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ClusterProcessMap": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"api.ClusterProcessReallocate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"process_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.ProcessID"
|
||||
}
|
||||
},
|
||||
"target_node_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ClusterRaft": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -5122,6 +5325,20 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ClusterStoreNode": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.Command": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -6614,6 +6831,17 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ProcessID": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ProcessReport": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@ -201,6 +201,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/db/node": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "List of nodes in the cluster DB",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v16.?.?"
|
||||
],
|
||||
"summary": "List nodes in the cluster DB",
|
||||
"operationId": "cluster-3-db-list-nodes",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.ClusterStoreNode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/db/policies": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -1373,6 +1402,107 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/node/{id}/state": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get the state of a node with the given ID",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v16.?.?"
|
||||
],
|
||||
"summary": "Get the state of a node with the given ID",
|
||||
"operationId": "cluster-3-get-node-state",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Node ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.ClusterNodeState"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Set the state of a node with the given ID",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v16.?.?"
|
||||
],
|
||||
"summary": "Set the state of a node with the given ID",
|
||||
"operationId": "cluster-3-set-node-state",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Node ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "State",
|
||||
"name": "config",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.ClusterNodeState"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/node/{id}/version": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -2031,6 +2161,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/reallocation": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Retrieve snapshot of the cluster DB",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v16.?.?"
|
||||
],
|
||||
"summary": "Retrieve snapshot of the cluster DB",
|
||||
"operationId": "cluster-3-reallocation",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Process reallocations",
|
||||
"name": "reallocations",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.ClusterProcessReallocate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/cluster/snapshot": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -5084,12 +5257,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ClusterNodeState": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"state"
|
||||
],
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"online",
|
||||
"maintenance",
|
||||
"leave"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ClusterProcessMap": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"api.ClusterProcessReallocate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"process_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.ProcessID"
|
||||
}
|
||||
},
|
||||
"target_node_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ClusterRaft": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -5114,6 +5317,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ClusterStoreNode": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.Command": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -6606,6 +6823,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ProcessID": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.ProcessReport": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@ -207,10 +207,30 @@ definitions:
|
||||
ncpu:
|
||||
type: number
|
||||
type: object
|
||||
api.ClusterNodeState:
|
||||
properties:
|
||||
state:
|
||||
enum:
|
||||
- online
|
||||
- maintenance
|
||||
- leave
|
||||
type: string
|
||||
required:
|
||||
- state
|
||||
type: object
|
||||
api.ClusterProcessMap:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
api.ClusterProcessReallocate:
|
||||
properties:
|
||||
process_ids:
|
||||
items:
|
||||
$ref: '#/definitions/api.ProcessID'
|
||||
type: array
|
||||
target_node_id:
|
||||
type: string
|
||||
type: object
|
||||
api.ClusterRaft:
|
||||
properties:
|
||||
address:
|
||||
@ -227,6 +247,15 @@ definitions:
|
||||
state:
|
||||
type: string
|
||||
type: object
|
||||
api.ClusterStoreNode:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
api.Command:
|
||||
properties:
|
||||
command:
|
||||
@ -1231,6 +1260,13 @@ definitions:
|
||||
format: uint64
|
||||
type: integer
|
||||
type: object
|
||||
api.ProcessID:
|
||||
properties:
|
||||
domain:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
type: object
|
||||
api.ProcessReport:
|
||||
properties:
|
||||
created_at:
|
||||
@ -2689,6 +2725,24 @@ paths:
|
||||
summary: List locks in the cluster DB
|
||||
tags:
|
||||
- v16.?.?
|
||||
/api/v3/cluster/db/node:
|
||||
get:
|
||||
description: List of nodes in the cluster DB
|
||||
operationId: cluster-3-db-list-nodes
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/api.ClusterStoreNode'
|
||||
type: array
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: List nodes in the cluster DB
|
||||
tags:
|
||||
- v16.?.?
|
||||
/api/v3/cluster/db/policies:
|
||||
get:
|
||||
description: List of policies in the cluster
|
||||
@ -3451,6 +3505,71 @@ paths:
|
||||
summary: List of processes in the cluster on a node
|
||||
tags:
|
||||
- v16.?.?
|
||||
/api/v3/cluster/node/{id}/state:
|
||||
get:
|
||||
description: Get the state of a node with the given ID
|
||||
operationId: cluster-3-get-node-state
|
||||
parameters:
|
||||
- description: Node ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.ClusterNodeState'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/api.Error'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Get the state of a node with the given ID
|
||||
tags:
|
||||
- v16.?.?
|
||||
put:
|
||||
description: Set the state of a node with the given ID
|
||||
operationId: cluster-3-set-node-state
|
||||
parameters:
|
||||
- description: Node ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: State
|
||||
in: body
|
||||
name: config
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/api.ClusterNodeState'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/api.Error'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/api.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/api.Error'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Set the state of a node with the given ID
|
||||
tags:
|
||||
- v16.?.?
|
||||
/api/v3/cluster/node/{id}/version:
|
||||
get:
|
||||
description: List a proxy node by its ID
|
||||
@ -3893,6 +4012,33 @@ paths:
|
||||
summary: Probe a process in the cluster
|
||||
tags:
|
||||
- v16.?.?
|
||||
/api/v3/cluster/reallocation:
|
||||
put:
|
||||
description: Retrieve snapshot of the cluster DB
|
||||
operationId: cluster-3-reallocation
|
||||
parameters:
|
||||
- description: Process reallocations
|
||||
in: body
|
||||
name: reallocations
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/api.ClusterProcessReallocate'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/api.Error'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Retrieve snapshot of the cluster DB
|
||||
tags:
|
||||
- v16.?.?
|
||||
/api/v3/cluster/snapshot:
|
||||
get:
|
||||
description: Retrieve snapshot of the cluster DB
|
||||
|
||||
@ -23,24 +23,24 @@ type Codec struct {
|
||||
Decoders []string
|
||||
}
|
||||
|
||||
func (a Codec) Equal(b Codec) bool {
|
||||
func (a Codec) Equal(b Codec) error {
|
||||
if a.Id != b.Id {
|
||||
return false
|
||||
return fmt.Errorf("id expected: %s, actual: %s", a.Id, b.Id)
|
||||
}
|
||||
|
||||
if a.Name != b.Name {
|
||||
return false
|
||||
return fmt.Errorf("name expected: %s, actual: %s", a.Name, b.Name)
|
||||
}
|
||||
|
||||
if !slices.EqualComparableElements(a.Encoders, b.Encoders) {
|
||||
return false
|
||||
if err := slices.EqualComparableElements(a.Encoders, b.Encoders); err != nil {
|
||||
return fmt.Errorf("codec %s encoders: %w", a.Name, err)
|
||||
}
|
||||
|
||||
if !slices.EqualComparableElements(a.Decoders, b.Decoders) {
|
||||
return false
|
||||
if err := slices.EqualComparableElements(a.Decoders, b.Decoders); err != nil {
|
||||
return fmt.Errorf("codec %s decoders: %w", a.Name, err)
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
type ffCodecs struct {
|
||||
@ -49,20 +49,20 @@ type ffCodecs struct {
|
||||
Subtitle []Codec
|
||||
}
|
||||
|
||||
func (a ffCodecs) Equal(b ffCodecs) bool {
|
||||
if !slices.EqualEqualerElements(a.Audio, b.Audio) {
|
||||
return false
|
||||
func (a ffCodecs) Equal(b ffCodecs) error {
|
||||
if err := slices.EqualEqualerElements(a.Audio, b.Audio); err != nil {
|
||||
return fmt.Errorf("audio: %w", err)
|
||||
}
|
||||
|
||||
if !slices.EqualEqualerElements(a.Video, b.Video) {
|
||||
return false
|
||||
if err := slices.EqualEqualerElements(a.Video, b.Video); err != nil {
|
||||
return fmt.Errorf("video: %w", err)
|
||||
}
|
||||
|
||||
if !slices.EqualEqualerElements(a.Subtitle, b.Subtitle) {
|
||||
return false
|
||||
if err := slices.EqualEqualerElements(a.Subtitle, b.Subtitle); err != nil {
|
||||
return fmt.Errorf("subtitle: %w", err)
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// HWDevice represents a hardware device (e.g. USB device)
|
||||
@ -73,24 +73,24 @@ type HWDevice struct {
|
||||
Media string
|
||||
}
|
||||
|
||||
func (a HWDevice) Equal(b HWDevice) bool {
|
||||
func (a HWDevice) Equal(b HWDevice) error {
|
||||
if a.Id != b.Id {
|
||||
return false
|
||||
return fmt.Errorf("id expected: %s, actual: %s", a.Id, b.Id)
|
||||
}
|
||||
|
||||
if a.Name != b.Name {
|
||||
return false
|
||||
return fmt.Errorf("name expected: %s, actual: %s", a.Name, b.Name)
|
||||
}
|
||||
|
||||
if a.Extra != b.Extra {
|
||||
return false
|
||||
return fmt.Errorf("extra expected: %s, actual: %s", a.Extra, b.Extra)
|
||||
}
|
||||
|
||||
if a.Media != b.Media {
|
||||
return false
|
||||
return fmt.Errorf("media expected: %s, actual: %s", a.Media, b.Media)
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Device represents a type of device (e.g. V4L2) including connected actual devices
|
||||
@ -100,20 +100,20 @@ type Device struct {
|
||||
Devices []HWDevice
|
||||
}
|
||||
|
||||
func (a Device) Equal(b Device) bool {
|
||||
func (a Device) Equal(b Device) error {
|
||||
if a.Id != b.Id {
|
||||
return false
|
||||
return fmt.Errorf("id expected: %s, actual: %s", a.Id, b.Id)
|
||||
}
|
||||
|
||||
if a.Name != b.Name {
|
||||
return false
|
||||
return fmt.Errorf("name expected: %s, actual: %s", a.Name, b.Name)
|
||||
}
|
||||
|
||||
if !slices.EqualEqualerElements(a.Devices, b.Devices) {
|
||||
return false
|
||||
if err := slices.EqualEqualerElements(a.Devices, b.Devices); err != nil {
|
||||
return fmt.Errorf("hwdevice: %w", err)
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
type ffDevices struct {
|
||||
@ -121,16 +121,16 @@ type ffDevices struct {
|
||||
Muxers []Device
|
||||
}
|
||||
|
||||
func (a ffDevices) Equal(b ffDevices) bool {
|
||||
if !slices.EqualEqualerElements(a.Demuxers, b.Demuxers) {
|
||||
return false
|
||||
func (a ffDevices) Equal(b ffDevices) error {
|
||||
if err := slices.EqualEqualerElements(a.Demuxers, b.Demuxers); err != nil {
|
||||
return fmt.Errorf("demuxers: %w", err)
|
||||
}
|
||||
|
||||
if !slices.EqualEqualerElements(a.Muxers, b.Muxers) {
|
||||
return false
|
||||
if err := slices.EqualEqualerElements(a.Muxers, b.Muxers); err != nil {
|
||||
return fmt.Errorf("muxers: %w", err)
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format represents a supported format (e.g. flv)
|
||||
@ -144,16 +144,16 @@ type ffFormats struct {
|
||||
Muxers []Format
|
||||
}
|
||||
|
||||
func (a ffFormats) Equal(b ffFormats) bool {
|
||||
if !slices.EqualComparableElements(a.Demuxers, b.Demuxers) {
|
||||
return false
|
||||
func (a ffFormats) Equal(b ffFormats) error {
|
||||
if err := slices.EqualComparableElements(a.Demuxers, b.Demuxers); err != nil {
|
||||
return fmt.Errorf("demuxers: %w", err)
|
||||
}
|
||||
|
||||
if !slices.EqualComparableElements(a.Muxers, b.Muxers) {
|
||||
return false
|
||||
if err := slices.EqualComparableElements(a.Muxers, b.Muxers); err != nil {
|
||||
return fmt.Errorf("muxers: %w", err)
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Protocol represents a supported protocol (e.g. rtsp)
|
||||
@ -167,16 +167,16 @@ type ffProtocols struct {
|
||||
Output []Protocol
|
||||
}
|
||||
|
||||
func (a ffProtocols) Equal(b ffProtocols) bool {
|
||||
if !slices.EqualComparableElements(a.Input, b.Input) {
|
||||
return false
|
||||
func (a ffProtocols) Equal(b ffProtocols) error {
|
||||
if err := slices.EqualComparableElements(a.Input, b.Input); err != nil {
|
||||
return fmt.Errorf("input: %w", err)
|
||||
}
|
||||
|
||||
if !slices.EqualComparableElements(a.Output, b.Output) {
|
||||
return false
|
||||
if err := slices.EqualComparableElements(a.Output, b.Output); err != nil {
|
||||
return fmt.Errorf("output: %w", err)
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
type HWAccel struct {
|
||||
@ -204,24 +204,24 @@ type ffmpeg struct {
|
||||
Libraries []Library
|
||||
}
|
||||
|
||||
func (a ffmpeg) Equal(b ffmpeg) bool {
|
||||
func (a ffmpeg) Equal(b ffmpeg) error {
|
||||
if a.Version != b.Version {
|
||||
return false
|
||||
return fmt.Errorf("version expected: %s, actual: %s", a.Version, b.Version)
|
||||
}
|
||||
|
||||
if a.Compiler != b.Compiler {
|
||||
return false
|
||||
return fmt.Errorf("compiler expected: %s, actual: %s", a.Compiler, b.Compiler)
|
||||
}
|
||||
|
||||
if a.Configuration != b.Configuration {
|
||||
return false
|
||||
return fmt.Errorf("configuration expected: %s, actual: %s", a.Configuration, b.Configuration)
|
||||
}
|
||||
|
||||
if !slices.EqualComparableElements(a.Libraries, b.Libraries) {
|
||||
return false
|
||||
if err := slices.EqualComparableElements(a.Libraries, b.Libraries); err != nil {
|
||||
return fmt.Errorf("libraries: %w", err)
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skills are the detected capabilities of a ffmpeg binary
|
||||
@ -237,36 +237,36 @@ type Skills struct {
|
||||
Protocols ffProtocols
|
||||
}
|
||||
|
||||
func (a Skills) Equal(b Skills) bool {
|
||||
if !a.FFmpeg.Equal(b.FFmpeg) {
|
||||
return false
|
||||
func (a Skills) Equal(b Skills) error {
|
||||
if err := a.FFmpeg.Equal(b.FFmpeg); err != nil {
|
||||
return fmt.Errorf("ffmpeg: %w", err)
|
||||
}
|
||||
|
||||
if !slices.EqualComparableElements(a.Filters, b.Filters) {
|
||||
return false
|
||||
if err := slices.EqualComparableElements(a.Filters, b.Filters); err != nil {
|
||||
return fmt.Errorf("filters: %w", err)
|
||||
}
|
||||
|
||||
if !slices.EqualComparableElements(a.HWAccels, b.HWAccels) {
|
||||
return false
|
||||
if err := slices.EqualComparableElements(a.HWAccels, b.HWAccels); err != nil {
|
||||
return fmt.Errorf("hwaccels: %w", err)
|
||||
}
|
||||
|
||||
if !a.Codecs.Equal(b.Codecs) {
|
||||
return false
|
||||
if err := a.Codecs.Equal(b.Codecs); err != nil {
|
||||
return fmt.Errorf("codecs: %w", err)
|
||||
}
|
||||
|
||||
if !a.Devices.Equal(b.Devices) {
|
||||
return false
|
||||
if err := a.Devices.Equal(b.Devices); err != nil {
|
||||
return fmt.Errorf("devices: %w", err)
|
||||
}
|
||||
|
||||
if !a.Formats.Equal(b.Formats) {
|
||||
return false
|
||||
if err := a.Formats.Equal(b.Formats); err != nil {
|
||||
return fmt.Errorf("formats: %w", err)
|
||||
}
|
||||
|
||||
if !a.Protocols.Equal(b.Protocols) {
|
||||
return false
|
||||
if err := a.Protocols.Equal(b.Protocols); err != nil {
|
||||
return fmt.Errorf("protocols: %w", err)
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// New returns all skills that ffmpeg provides
|
||||
|
||||
@ -321,28 +321,28 @@ func TestNew(t *testing.T) {
|
||||
func TestEqualEmptySkills(t *testing.T) {
|
||||
s := Skills{}
|
||||
|
||||
ok := s.Equal(s)
|
||||
require.True(t, ok)
|
||||
err := s.Equal(s)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEuqalSkills(t *testing.T) {
|
||||
func TestEqualSkills(t *testing.T) {
|
||||
binary, err := testhelper.BuildBinary("ffmpeg", "../../internal/testhelper")
|
||||
require.NoError(t, err, "Failed to build helper program")
|
||||
|
||||
s1, err := New(binary)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok := s1.Equal(s1)
|
||||
require.True(t, ok)
|
||||
err = s1.Equal(s1)
|
||||
require.NoError(t, err)
|
||||
|
||||
s2, err := New(binary)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok = s1.Equal(s2)
|
||||
require.True(t, ok)
|
||||
err = s1.Equal(s2)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok = s1.Equal(Skills{})
|
||||
require.False(t, ok)
|
||||
err = s1.Equal(Skills{})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPatchVersion(t *testing.T) {
|
||||
|
||||
@ -10,6 +10,12 @@ type globber struct {
|
||||
glob glob.Glob
|
||||
}
|
||||
|
||||
func MustCompile(pattern string, separators ...rune) Glob {
|
||||
g := glob.MustCompile(pattern, separators...)
|
||||
|
||||
return &globber{glob: g}
|
||||
}
|
||||
|
||||
func Compile(pattern string, separators ...rune) (Glob, error) {
|
||||
g, err := glob.Compile(pattern, separators...)
|
||||
if err != nil {
|
||||
|
||||
3
go.mod
3
go.mod
@ -12,13 +12,13 @@ require (
|
||||
github.com/atrox/haikunatorgo/v2 v2.0.1
|
||||
github.com/caddyserver/certmagic v0.21.3
|
||||
github.com/casbin/casbin/v2 v2.90.0
|
||||
github.com/datarhei/core-client-go/v16 v16.11.1-0.20240429143858-23ad5985b894
|
||||
github.com/datarhei/gosrt v0.6.0
|
||||
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e
|
||||
github.com/fujiwara/shapeio v1.0.0
|
||||
github.com/go-playground/validator/v10 v10.21.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/goccy/go-json v0.10.3
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/gops v0.3.28
|
||||
github.com/google/uuid v1.6.0
|
||||
@ -76,7 +76,6 @@ require (
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@ -51,8 +51,6 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/datarhei/core-client-go/v16 v16.11.1-0.20240429143858-23ad5985b894 h1:ZQCTobOGpzfuZxgMWsZviFSXfI5QuttkTgPQz1PKbhU=
|
||||
github.com/datarhei/core-client-go/v16 v16.11.1-0.20240429143858-23ad5985b894/go.mod h1:Mu2bHqvGJe46KvAhY2ElohuQYhHB64PZeaCNDv6C5b8=
|
||||
github.com/datarhei/gosrt v0.6.0 h1:HrrXAw90V78ok4WMIhX6se1aTHPCn82Sg2hj+PhdmGc=
|
||||
github.com/datarhei/gosrt v0.6.0/go.mod h1:fsOWdLSHUHShHjgi/46h6wjtdQrtnSdAQFnlas8ONxs=
|
||||
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e h1:Qc/0D4xvXrazFkoi/4UGqO15yQ1JN5I8h7RwdzCLgTY=
|
||||
|
||||
@ -25,6 +25,10 @@ type ClusterNode struct {
|
||||
Resources ClusterNodeResources `json:"resources"`
|
||||
}
|
||||
|
||||
type ClusterNodeState struct {
|
||||
State string `json:"state" validate:"required" enums:"online,maintenance,leave" jsonschema:"enum=online,enum=maintenance,enum=leave"`
|
||||
}
|
||||
|
||||
type ClusterNodeCore struct {
|
||||
Address string `json:"address"`
|
||||
Status string `json:"status"`
|
||||
@ -89,3 +93,14 @@ type ClusterKVSValue struct {
|
||||
type ClusterKVS map[string]ClusterKVSValue
|
||||
|
||||
type ClusterProcessMap map[string]string
|
||||
|
||||
type ClusterProcessReallocate struct {
|
||||
TargetNodeID string `json:"target_node_id"`
|
||||
Processes []ProcessID `json:"process_ids"`
|
||||
}
|
||||
|
||||
type ClusterStoreNode struct {
|
||||
ID string `json:"id"`
|
||||
State string `json:"state"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
@ -7,6 +7,11 @@ import (
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
)
|
||||
|
||||
type ProcessID struct {
|
||||
ID string `json:"id"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
// Process represents all information on a process
|
||||
type Process struct {
|
||||
ID string `json:"id" jsonschema:"minLength=1"`
|
||||
|
||||
@ -29,7 +29,7 @@ type ProcessReport struct {
|
||||
}
|
||||
|
||||
// Unmarshal converts a restream log to a report
|
||||
func (report *ProcessReport) Unmarshal(l *app.Log) {
|
||||
func (report *ProcessReport) Unmarshal(l *app.Report) {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package coreclient
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -12,12 +12,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/datarhei/core-client-go/v16/api"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/glob"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
"github.com/datarhei/core/v16/restream/app"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/gobwas/glob"
|
||||
jwtgo "github.com/golang-jwt/jwt/v4"
|
||||
"github.com/klauspost/compress/gzip"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
@ -50,24 +50,6 @@ type RestClient interface {
|
||||
|
||||
About(cached bool) (api.About, error) // GET /
|
||||
|
||||
Config() (int64, api.Config, error) // GET /v3/config
|
||||
ConfigSet(config interface{}) error // POST /v3/config
|
||||
ConfigReload() error // GET /v3/config/reload
|
||||
|
||||
Graph(query api.GraphQuery) (api.GraphResponse, error) // POST /graph
|
||||
|
||||
DiskFSList(sort, order string) ([]api.FileInfo, error) // GET /v3/fs/disk
|
||||
DiskFSHasFile(path string) bool // HEAD /v3/fs/disk/{path}
|
||||
DiskFSGetFile(path string) (io.ReadCloser, error) // GET /v3/fs/disk/{path}
|
||||
DiskFSDeleteFile(path string) error // DELETE /v3/fs/disk/{path}
|
||||
DiskFSAddFile(path string, data io.Reader) error // PUT /v3/fs/disk/{path}
|
||||
|
||||
MemFSList(sort, order string) ([]api.FileInfo, error) // GET /v3/fs/mem
|
||||
MemFSHasFile(path string) bool // HEAD /v3/fs/mem/{path}
|
||||
MemFSGetFile(path string) (io.ReadCloser, error) // GET /v3/fs/mem/{path}
|
||||
MemFSDeleteFile(path string) error // DELETE /v3/fs/mem/{path}
|
||||
MemFSAddFile(path string, data io.Reader) error // PUT /v3/fs/mem/{path}
|
||||
|
||||
FilesystemList(storage, pattern, sort, order string) ([]api.FileInfo, error) // GET /v3/fs/{storage}
|
||||
FilesystemHasFile(storage, path string) bool // HEAD /v3/fs/{storage}/{path}
|
||||
FilesystemGetFile(storage, path string) (io.ReadCloser, error) // GET /v3/fs/{storage}/{path}
|
||||
@ -75,96 +57,28 @@ type RestClient interface {
|
||||
FilesystemDeleteFile(storage, path string) error // DELETE /v3/fs/{storage}/{path}
|
||||
FilesystemAddFile(storage, path string, data io.Reader) error // PUT /v3/fs/{storage}/{path}
|
||||
|
||||
Log() ([]api.LogEvent, error) // GET /v3/log
|
||||
Events(ctx context.Context, filters api.EventFilters) (<-chan api.Event, error) // POST /v3/events
|
||||
|
||||
Metadata(key string) (api.Metadata, error) // GET /v3/metadata/{key}
|
||||
MetadataSet(key string, metadata api.Metadata) error // PUT /v3/metadata/{key}
|
||||
|
||||
MetricsList() ([]api.MetricsDescription, error) // GET /v3/metrics
|
||||
Metrics(query api.MetricsQuery) (api.MetricsResponse, error) // POST /v3/metrics
|
||||
|
||||
ProcessList(opts ProcessListOptions) ([]api.Process, error) // GET /v3/process
|
||||
ProcessAdd(p api.ProcessConfig) error // POST /v3/process
|
||||
Process(id ProcessID, filter []string) (api.Process, error) // GET /v3/process/{id}
|
||||
ProcessUpdate(id ProcessID, p api.ProcessConfig) error // PUT /v3/process/{id}
|
||||
ProcessDelete(id ProcessID) error // DELETE /v3/process/{id}
|
||||
ProcessCommand(id ProcessID, command string) error // PUT /v3/process/{id}/command
|
||||
ProcessProbe(id ProcessID) (api.Probe, error) // GET /v3/process/{id}/probe
|
||||
ProcessProbeConfig(config api.ProcessConfig) (api.Probe, error) // POST /v3/process/probe
|
||||
ProcessConfig(id ProcessID) (api.ProcessConfig, error) // GET /v3/process/{id}/config
|
||||
ProcessReport(id ProcessID) (api.ProcessReport, error) // GET /v3/process/{id}/report
|
||||
ProcessState(id ProcessID) (api.ProcessState, error) // GET /v3/process/{id}/state
|
||||
ProcessMetadata(id ProcessID, key string) (api.Metadata, error) // GET /v3/process/{id}/metadata/{key}
|
||||
ProcessMetadataSet(id ProcessID, key string, metadata api.Metadata) error // PUT /v3/process/{id}/metadata/{key}
|
||||
|
||||
PlayoutStatus(id ProcessID, inputID string) (api.PlayoutStatus, error) // GET /v3/process/{id}/playout/{inputid}/status
|
||||
|
||||
IdentitiesList() ([]api.IAMUser, error) // GET /v3/iam/user
|
||||
Identity(name string) (api.IAMUser, error) // GET /v3/iam/user/{name}
|
||||
IdentityAdd(u api.IAMUser) error // POST /v3/iam/user
|
||||
IdentityUpdate(name string, u api.IAMUser) error // PUT /v3/iam/user/{name}
|
||||
IdentitySetPolicies(name string, p []api.IAMPolicy) error // PUT /v3/iam/user/{name}/policy
|
||||
IdentityDelete(name string) error // DELETE /v3/iam/user/{name}
|
||||
|
||||
Cluster() (*api.ClusterAboutV1, *api.ClusterAboutV2, error) // GET /v3/cluster
|
||||
ClusterHealthy() (bool, error) // GET /v3/cluster/healthy
|
||||
ClusterSnapshot() (io.ReadCloser, error) // GET /v3/cluster/snapshot
|
||||
ClusterLeave() error // PUT /v3/cluster/leave
|
||||
ClusterTransferLeadership(id string) error // PUT /v3/cluster/transfer/{id}
|
||||
|
||||
ClusterNodeList() ([]api.ClusterNode, error) // GET /v3/cluster/node
|
||||
ClusterNode(id string) (api.ClusterNode, error) // GET /v3/cluster/node/{id}
|
||||
ClusterNodeFiles(id string) (api.ClusterNodeFiles, error) // GET /v3/cluster/node/{id}/files
|
||||
ClusterNodeProcessList(id string, opts ProcessListOptions) ([]api.Process, error) // GET /v3/cluster/node/{id}/process
|
||||
ClusterNodeVersion(id string) (api.Version, error) // GET /v3/cluster/node/{id}/version
|
||||
ClusterNodeFilesystemList(id, storage, pattern, sort, order string) ([]api.FileInfo, error) // GET /v3/cluster/node/{id}/fs/{storage}
|
||||
ClusterNodeFilesystemDeleteFile(id, storage, path string) error // DELETE /v3/cluster/node/{id}/fs/{storage}/{path}
|
||||
ClusterNodeFilesystemPutFile(id, storage, path string, data io.Reader) error // PUT /v3/cluster/node/{id}/fs/{storage}/{path}
|
||||
ClusterNodeFilesystemGetFile(id, storage, path string) (io.ReadCloser, error) // GET /v3/cluster/node/{id}/fs/{storage}/{path}
|
||||
|
||||
ClusterDBProcessList() ([]api.Process, error) // GET /v3/cluster/db/process
|
||||
ClusterDBProcess(id ProcessID) (api.Process, error) // GET /v3/cluster/db/process/{id}
|
||||
ClusterDBUserList() ([]api.IAMUser, error) // GET /v3/cluster/db/user
|
||||
ClusterDBUser(name string) (api.IAMUser, error) // GET /v3/cluster/db/user/{name}
|
||||
ClusterDBPolicies() ([]api.IAMPolicy, error) // GET /v3/cluster/db/policies
|
||||
ClusterDBLocks() ([]api.ClusterLock, error) // GET /v3/cluster/db/locks
|
||||
ClusterDBKeyValues() (api.ClusterKVS, error) // GET /v3/cluster/db/kv
|
||||
ClusterDBProcessMap() (api.ClusterProcessMap, error) // GET /v3/cluster/db/map/process
|
||||
|
||||
ClusterFilesystemList(name, pattern, sort, order string) ([]api.FileInfo, error) // GET /v3/cluster/fs/{storage}
|
||||
|
||||
ClusterProcessList(opts ProcessListOptions) ([]api.Process, error) // GET /v3/cluster/process
|
||||
ClusterProcess(id ProcessID, filter []string) (api.Process, error) // POST /v3/cluster/process
|
||||
ClusterProcessAdd(p api.ProcessConfig) error // GET /v3/cluster/process/{id}
|
||||
ClusterProcessUpdate(id ProcessID, p api.ProcessConfig) error // PUT /v3/cluster/process/{id}
|
||||
ClusterProcessDelete(id ProcessID) error // DELETE /v3/cluster/process/{id}
|
||||
ClusterProcessCommand(id ProcessID, command string) error // PUT /v3/cluster/process/{id}/command
|
||||
ClusterProcessMetadata(id ProcessID, key string) (api.Metadata, error) // GET /v3/cluster/process/{id}/metadata/{key}
|
||||
ClusterProcessMetadataSet(id ProcessID, key string, metadata api.Metadata) error // PUT /v3/cluster/process/{id}/metadata/{key}
|
||||
ClusterProcessProbe(id ProcessID) (api.Probe, error) // GET /v3/cluster/process/{id}/probe
|
||||
ClusterProcessProbeConfig(config api.ProcessConfig, coreid string) (api.Probe, error) // POST /v3/cluster/process/probe
|
||||
|
||||
ClusterIdentitiesList() ([]api.IAMUser, error) // GET /v3/cluster/iam/user
|
||||
ClusterIdentity(name string) (api.IAMUser, error) // GET /v3/cluster/iam/user/{name}
|
||||
ClusterIdentityAdd(u api.IAMUser) error // POST /v3/cluster/iam/user
|
||||
ClusterIdentityUpdate(name string, u api.IAMUser) error // PUT /v3/cluster/iam/user/{name}
|
||||
ClusterIdentitySetPolicies(name string, p []api.IAMPolicy) error // PUT /v3/cluster/iam/user/{name}/policy
|
||||
ClusterIdentityDelete(name string) error // DELETE /v3/cluster/iam/user/{name}
|
||||
ClusterIAMReload() error // PUT /v3/cluster/iam/reload
|
||||
ProcessList(opts ProcessListOptions) ([]api.Process, error) // GET /v3/process
|
||||
ProcessAdd(p *app.Config, metadata map[string]interface{}) error // POST /v3/process
|
||||
Process(id app.ProcessID, filter []string) (api.Process, error) // GET /v3/process/{id}
|
||||
ProcessUpdate(id app.ProcessID, p *app.Config, metadata map[string]interface{}) error // PUT /v3/process/{id}
|
||||
ProcessDelete(id app.ProcessID) error // DELETE /v3/process/{id}
|
||||
ProcessCommand(id app.ProcessID, command string) error // PUT /v3/process/{id}/command
|
||||
ProcessProbe(id app.ProcessID) (api.Probe, error) // GET /v3/process/{id}/probe
|
||||
ProcessProbeConfig(config *app.Config) (api.Probe, error) // POST /v3/process/probe
|
||||
ProcessConfig(id app.ProcessID) (api.ProcessConfig, error) // GET /v3/process/{id}/config
|
||||
ProcessReport(id app.ProcessID) (api.ProcessReport, error) // GET /v3/process/{id}/report
|
||||
ProcessState(id app.ProcessID) (api.ProcessState, error) // GET /v3/process/{id}/state
|
||||
ProcessMetadata(id app.ProcessID, key string) (api.Metadata, error) // GET /v3/process/{id}/metadata/{key}
|
||||
ProcessMetadataSet(id app.ProcessID, key string, metadata api.Metadata) error // PUT /v3/process/{id}/metadata/{key}
|
||||
|
||||
RTMPChannels() ([]api.RTMPChannel, error) // GET /v3/rtmp
|
||||
SRTChannels() ([]api.SRTChannel, error) // GET /v3/srt
|
||||
SRTChannelsRaw() ([]byte, error) // GET /v3/srt
|
||||
|
||||
Sessions(collectors []string) (api.SessionsSummary, error) // GET /v3/session
|
||||
SessionsActive(collectors []string) (api.SessionsActive, error) // GET /v3/session/active
|
||||
SessionToken(name string, req []api.SessionTokenRequest) ([]api.SessionTokenRequest, error) // PUT /v3/session/token/{username}
|
||||
|
||||
Skills() (api.Skills, error) // GET /v3/skills
|
||||
SkillsReload() error // GET /v3/skills/reload
|
||||
|
||||
WidgetProcess(id ProcessID) (api.WidgetProcess, error) // GET /v3/widget/process/{id}
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
@ -448,6 +362,14 @@ func New(config Config) (RestClient, error) {
|
||||
path: mustNewGlob("/v3/cluster/node/*/fs/*/**"),
|
||||
constraint: mustNewConstraint("^16.14.0"),
|
||||
},
|
||||
{
|
||||
path: mustNewGlob("/v3/cluster/db/node"),
|
||||
constraint: mustNewConstraint("^16.14.0"),
|
||||
},
|
||||
{
|
||||
path: mustNewGlob("/v3/cluster/node/*/state"),
|
||||
constraint: mustNewConstraint("^16.14.0"),
|
||||
},
|
||||
},
|
||||
"POST": {
|
||||
{
|
||||
@ -524,6 +446,14 @@ func New(config Config) (RestClient, error) {
|
||||
path: mustNewGlob("/v3/cluster/node/*/fs/*/**"),
|
||||
constraint: mustNewConstraint("^16.14.0"),
|
||||
},
|
||||
{
|
||||
path: mustNewGlob("/v3/cluster/reallocate"),
|
||||
constraint: mustNewConstraint("^16.14.0"),
|
||||
},
|
||||
{
|
||||
path: mustNewGlob("/v3/cluster/node/*/state"),
|
||||
constraint: mustNewConstraint("^16.14.0"),
|
||||
},
|
||||
},
|
||||
"DELETE": {
|
||||
{
|
||||
@ -614,6 +544,9 @@ func (r *restclient) Address() string {
|
||||
|
||||
func (r *restclient) About(cached bool) (api.About, error) {
|
||||
if cached {
|
||||
r.aboutLock.RLock()
|
||||
defer r.aboutLock.RUnlock()
|
||||
|
||||
return r.about, nil
|
||||
}
|
||||
|
||||
@ -622,7 +555,22 @@ func (r *restclient) About(cached bool) (api.About, error) {
|
||||
return api.About{}, err
|
||||
}
|
||||
|
||||
if r.accessToken.IsSet() && len(about.ID) == 0 {
|
||||
if err := r.refresh(); err != nil {
|
||||
if err := r.login(); err != nil {
|
||||
return api.About{}, err
|
||||
}
|
||||
}
|
||||
|
||||
about, err = r.info()
|
||||
if err != nil {
|
||||
return api.About{}, err
|
||||
}
|
||||
}
|
||||
|
||||
r.aboutLock.Lock()
|
||||
r.about = about
|
||||
r.aboutLock.Unlock()
|
||||
|
||||
return about, nil
|
||||
}
|
||||
@ -821,7 +769,15 @@ func (r *restclient) info() (api.About, error) {
|
||||
return api.About{}, err
|
||||
}
|
||||
|
||||
if r.accessToken.IsSet() && !r.accessToken.IsExpired() {
|
||||
if r.accessToken.IsSet() {
|
||||
if r.accessToken.IsExpired() {
|
||||
if err := r.refresh(); err != nil {
|
||||
if err := r.login(); err != nil {
|
||||
return api.About{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+r.accessToken.String())
|
||||
}
|
||||
|
||||
@ -942,7 +898,7 @@ func (r *restclient) stream(ctx context.Context, method, path string, query *url
|
||||
return nil, e
|
||||
}
|
||||
|
||||
e.Body = data
|
||||
//e.Body = data
|
||||
|
||||
err = json.Unmarshal(data, &e)
|
||||
if err != nil {
|
||||
@ -967,7 +923,7 @@ func (r *restclient) call(method, path string, query *url.Values, header http.He
|
||||
|
||||
body, err := r.stream(ctx, method, path, query, header, contentType, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s %s: %w", method, path, err)
|
||||
}
|
||||
|
||||
defer body.Close()
|
||||
@ -1,4 +1,4 @@
|
||||
package coreclient
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -6,9 +6,8 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/datarhei/core-client-go/v16/api"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
)
|
||||
|
||||
func (r *restclient) Events(ctx context.Context, filters api.EventFilters) (<-chan api.Event, error) {
|
||||
@ -1,4 +1,4 @@
|
||||
package coreclient
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -8,9 +8,8 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/datarhei/core-client-go/v16/api"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
)
|
||||
|
||||
const (
|
||||
256
http/client/process.go
Normal file
256
http/client/process.go
Normal file
@ -0,0 +1,256 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
"github.com/datarhei/core/v16/restream/app"
|
||||
)
|
||||
|
||||
type ProcessListOptions struct {
|
||||
ID []string
|
||||
Filter []string
|
||||
Domain string
|
||||
Reference string
|
||||
IDPattern string
|
||||
RefPattern string
|
||||
OwnerPattern string
|
||||
DomainPattern string
|
||||
}
|
||||
|
||||
func (p *ProcessListOptions) Query() *url.Values {
|
||||
values := &url.Values{}
|
||||
values.Set("id", strings.Join(p.ID, ","))
|
||||
values.Set("filter", strings.Join(p.Filter, ","))
|
||||
values.Set("domain", p.Domain)
|
||||
values.Set("reference", p.Reference)
|
||||
values.Set("idpattern", p.IDPattern)
|
||||
values.Set("refpattern", p.RefPattern)
|
||||
values.Set("ownerpattern", p.OwnerPattern)
|
||||
values.Set("domainpattern", p.DomainPattern)
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessList(opts ProcessListOptions) ([]api.Process, error) {
|
||||
var processes []api.Process
|
||||
|
||||
data, err := r.call("GET", "/v3/process", opts.Query(), nil, "", nil)
|
||||
if err != nil {
|
||||
return processes, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &processes)
|
||||
|
||||
return processes, err
|
||||
}
|
||||
|
||||
func (r *restclient) Process(id app.ProcessID, filter []string) (api.Process, error) {
|
||||
var info api.Process
|
||||
|
||||
values := &url.Values{}
|
||||
values.Set("filter", strings.Join(filter, ","))
|
||||
values.Set("domain", id.Domain)
|
||||
|
||||
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID), values, nil, "", nil)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &info)
|
||||
|
||||
return info, err
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessAdd(p *app.Config, metadata map[string]interface{}) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
config := api.ProcessConfig{}
|
||||
config.Unmarshal(p)
|
||||
config.Metadata = metadata
|
||||
|
||||
e := json.NewEncoder(&buf)
|
||||
e.Encode(config)
|
||||
|
||||
_, err := r.call("POST", "/v3/process", nil, nil, "application/json", &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessUpdate(id app.ProcessID, p *app.Config, metadata map[string]interface{}) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
config := api.ProcessConfig{}
|
||||
config.Unmarshal(p)
|
||||
config.Metadata = metadata
|
||||
|
||||
e := json.NewEncoder(&buf)
|
||||
e.Encode(config)
|
||||
|
||||
query := &url.Values{}
|
||||
query.Set("domain", id.Domain)
|
||||
|
||||
_, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID), query, nil, "application/json", &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessDelete(id app.ProcessID) error {
|
||||
query := &url.Values{}
|
||||
query.Set("domain", id.Domain)
|
||||
|
||||
r.call("DELETE", "/v3/process/"+url.PathEscape(id.ID), query, nil, "", nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessCommand(id app.ProcessID, command string) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
e := json.NewEncoder(&buf)
|
||||
e.Encode(api.Command{
|
||||
Command: command,
|
||||
})
|
||||
|
||||
query := &url.Values{}
|
||||
query.Set("domain", id.Domain)
|
||||
|
||||
_, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/command", query, nil, "application/json", &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessMetadata(id app.ProcessID, key string) (api.Metadata, error) {
|
||||
var m api.Metadata
|
||||
|
||||
path := "/v3/process/" + url.PathEscape(id.ID) + "/metadata"
|
||||
|
||||
if len(key) != 0 {
|
||||
path += "/" + url.PathEscape(key)
|
||||
}
|
||||
|
||||
query := &url.Values{}
|
||||
query.Set("domain", id.Domain)
|
||||
|
||||
data, err := r.call("GET", path, query, nil, "", nil)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &m)
|
||||
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessMetadataSet(id app.ProcessID, key string, metadata api.Metadata) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
e := json.NewEncoder(&buf)
|
||||
e.Encode(metadata)
|
||||
|
||||
query := &url.Values{}
|
||||
query.Set("domain", id.Domain)
|
||||
|
||||
_, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/metadata/"+url.PathEscape(key), query, nil, "application/json", &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessProbe(id app.ProcessID) (api.Probe, error) {
|
||||
var p api.Probe
|
||||
|
||||
query := &url.Values{}
|
||||
query.Set("domain", id.Domain)
|
||||
|
||||
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID)+"/probe", query, nil, "", nil)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &p)
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessProbeConfig(p *app.Config) (api.Probe, error) {
|
||||
var probe api.Probe
|
||||
var buf bytes.Buffer
|
||||
|
||||
config := api.ProcessConfig{}
|
||||
config.Unmarshal(p)
|
||||
|
||||
e := json.NewEncoder(&buf)
|
||||
e.Encode(config)
|
||||
|
||||
data, err := r.call("POST", "/v3/process/probe", nil, nil, "application/json", &buf)
|
||||
if err != nil {
|
||||
return probe, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &p)
|
||||
|
||||
return probe, err
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessConfig(id app.ProcessID) (api.ProcessConfig, error) {
|
||||
var p api.ProcessConfig
|
||||
|
||||
query := &url.Values{}
|
||||
query.Set("domain", id.Domain)
|
||||
|
||||
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID)+"/config", query, nil, "", nil)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &p)
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessReport(id app.ProcessID) (api.ProcessReport, error) {
|
||||
var p api.ProcessReport
|
||||
|
||||
query := &url.Values{}
|
||||
query.Set("domain", id.Domain)
|
||||
|
||||
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID)+"/report", query, nil, "", nil)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &p)
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (r *restclient) ProcessState(id app.ProcessID) (api.ProcessState, error) {
|
||||
var p api.ProcessState
|
||||
|
||||
query := &url.Values{}
|
||||
query.Set("domain", id.Domain)
|
||||
|
||||
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID)+"/state", query, nil, "", nil)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &p)
|
||||
|
||||
return p, err
|
||||
}
|
||||
@ -1,9 +1,8 @@
|
||||
package coreclient
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/datarhei/core-client-go/v16/api"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
)
|
||||
|
||||
func (r *restclient) RTMPChannels() ([]api.RTMPChannel, error) {
|
||||
@ -1,9 +1,8 @@
|
||||
package coreclient
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/datarhei/core-client-go/v16/api"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
)
|
||||
|
||||
func (r *restclient) Skills() (api.Skills, error) {
|
||||
@ -1,9 +1,8 @@
|
||||
package coreclient
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/datarhei/core-client-go/v16/api"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
)
|
||||
|
||||
func (r *restclient) SRTChannels() ([]api.SRTChannel, error) {
|
||||
@ -6,7 +6,7 @@ import (
|
||||
gofs "io/fs"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster/proxy"
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
"github.com/datarhei/core/v16/io/fs"
|
||||
)
|
||||
|
||||
@ -18,10 +18,10 @@ type filesystem struct {
|
||||
fs.Filesystem
|
||||
|
||||
name string
|
||||
proxy proxy.ProxyReader
|
||||
proxy *node.Manager
|
||||
}
|
||||
|
||||
func NewClusterFS(name string, fs fs.Filesystem, proxy proxy.ProxyReader) Filesystem {
|
||||
func NewClusterFS(name string, fs fs.Filesystem, proxy *node.Manager) Filesystem {
|
||||
if proxy == nil {
|
||||
return fs
|
||||
}
|
||||
@ -42,14 +42,14 @@ func (fs *filesystem) Open(path string) fs.File {
|
||||
}
|
||||
|
||||
// Check if the file is available in the cluster
|
||||
size, lastModified, err := fs.proxy.GetFileInfo(fs.name, path)
|
||||
size, lastModified, err := fs.proxy.FilesystemGetFileInfo(fs.name, path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
file := &file{
|
||||
getFile: func(offset int64) (io.ReadCloser, error) {
|
||||
return fs.proxy.GetFile(fs.name, path, offset)
|
||||
return fs.proxy.FilesystemGetFile(fs.name, path, offset)
|
||||
},
|
||||
name: path,
|
||||
size: size,
|
||||
|
||||
@ -44,7 +44,7 @@ func (s *RawAVstreamSwap) UnmarshalPlayout(status playout.Status) {
|
||||
s.Lasterror = status.Swap.LastError
|
||||
}
|
||||
|
||||
func (p *Process) UnmarshalRestream(process *app.Process, state *app.State, report *app.Log, metadata map[string]interface{}) {
|
||||
func (p *Process) UnmarshalRestream(process *app.Process, state *app.State, report *app.Report, metadata map[string]interface{}) {
|
||||
p.ID = process.ID
|
||||
p.Type = "ffmpeg"
|
||||
p.Reference = process.Reference
|
||||
@ -189,7 +189,7 @@ func (a *AVStreamIo) UnmarshalRestream(io app.AVstreamIO) {
|
||||
a.SizeKb = scalars.Uint64(io.Size)
|
||||
}
|
||||
|
||||
func (r *ProcessReport) UnmarshalRestream(report *app.Log) {
|
||||
func (r *ProcessReport) UnmarshalRestream(report *app.Report) {
|
||||
r.CreatedAt = report.CreatedAt
|
||||
r.Prelude = report.Prelude
|
||||
r.Log = []*ProcessReportLogEntry{}
|
||||
@ -210,7 +210,7 @@ func (r *ProcessReport) UnmarshalRestream(report *app.Log) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ProcessReportHistoryEntry) UnmarshalRestream(entry app.LogHistoryEntry) {
|
||||
func (h *ProcessReportHistoryEntry) UnmarshalRestream(entry app.ReportHistoryEntry) {
|
||||
h.CreatedAt = entry.CreatedAt
|
||||
h.Prelude = entry.Prelude
|
||||
h.Log = []*ProcessReportLogEntry{}
|
||||
|
||||
@ -8,11 +8,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster"
|
||||
"github.com/datarhei/core/v16/cluster/proxy"
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
"github.com/datarhei/core/v16/http/handler/util"
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
"github.com/datarhei/core/v16/restream/app"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@ -20,7 +21,7 @@ import (
|
||||
// The ClusterHandler type provides handler functions for manipulating the cluster config.
|
||||
type ClusterHandler struct {
|
||||
cluster cluster.Cluster
|
||||
proxy proxy.ProxyReader
|
||||
proxy *node.Manager
|
||||
iam iam.IAM
|
||||
}
|
||||
|
||||
@ -28,7 +29,7 @@ type ClusterHandler struct {
|
||||
func NewCluster(cluster cluster.Cluster, iam iam.IAM) (*ClusterHandler, error) {
|
||||
h := &ClusterHandler{
|
||||
cluster: cluster,
|
||||
proxy: cluster.ProxyReader(),
|
||||
proxy: cluster.Manager(),
|
||||
iam: iam,
|
||||
}
|
||||
|
||||
@ -67,7 +68,7 @@ func (h *ClusterHandler) About(c echo.Context) error {
|
||||
Address: state.Leader.Address,
|
||||
ElectedSince: uint64(state.Leader.ElectedSince.Seconds()),
|
||||
},
|
||||
Status: state.Status,
|
||||
Status: state.State,
|
||||
Raft: api.ClusterRaft{
|
||||
Address: state.Raft.Address,
|
||||
State: state.Raft.State,
|
||||
@ -76,13 +77,13 @@ func (h *ClusterHandler) About(c echo.Context) error {
|
||||
LogTerm: state.Raft.LogTerm,
|
||||
LogIndex: state.Raft.LogIndex,
|
||||
},
|
||||
Nodes: []api.ClusterNode{},
|
||||
Version: state.Version.String(),
|
||||
Degraded: state.Degraded,
|
||||
Nodes: []api.ClusterNode{},
|
||||
Version: state.Version.String(),
|
||||
}
|
||||
|
||||
if state.DegradedErr != nil {
|
||||
about.DegradedErr = state.DegradedErr.Error()
|
||||
if state.Error != nil {
|
||||
about.Degraded = true
|
||||
about.DegradedErr = state.Error.Error()
|
||||
}
|
||||
|
||||
for _, node := range state.Nodes {
|
||||
@ -97,7 +98,7 @@ func (h *ClusterHandler) marshalClusterNode(node cluster.ClusterNode) api.Cluste
|
||||
ID: node.ID,
|
||||
Name: node.Name,
|
||||
Version: node.Version,
|
||||
Status: node.Status,
|
||||
Status: node.State,
|
||||
Voter: node.Voter,
|
||||
Leader: node.Leader,
|
||||
Address: node.Address,
|
||||
@ -107,7 +108,7 @@ func (h *ClusterHandler) marshalClusterNode(node cluster.ClusterNode) api.Cluste
|
||||
Latency: node.Latency.Seconds() * 1000,
|
||||
Core: api.ClusterNodeCore{
|
||||
Address: node.Core.Address,
|
||||
Status: node.Core.Status,
|
||||
Status: node.Core.State,
|
||||
LastContact: node.Core.LastContact.Seconds() * 1000,
|
||||
Latency: node.Core.Latency.Seconds() * 1000,
|
||||
Version: node.Core.Version,
|
||||
@ -147,12 +148,12 @@ func (h *ClusterHandler) marshalClusterNode(node cluster.ClusterNode) api.Cluste
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/healthy [get]
|
||||
func (h *ClusterHandler) Healthy(c echo.Context) error {
|
||||
degraded, _ := h.cluster.IsDegraded()
|
||||
hasLeader := h.cluster.HasRaftLeader()
|
||||
|
||||
return c.JSON(http.StatusOK, !degraded)
|
||||
return c.JSON(http.StatusOK, hasLeader)
|
||||
}
|
||||
|
||||
// Transfer the leadership to another node
|
||||
// TransferLeadership transfers the leadership to another node
|
||||
// @Summary Transfer the leadership to another node
|
||||
// @Description Transfer the leadership to another node
|
||||
// @Tags v16.?.?
|
||||
@ -228,3 +229,47 @@ func (h *ClusterHandler) GetSnapshot(c echo.Context) error {
|
||||
|
||||
return c.Stream(http.StatusOK, "application/octet-stream", r)
|
||||
}
|
||||
|
||||
// Reallocation issues reallocation requests of processes
|
||||
// @Summary Retrieve snapshot of the cluster DB
|
||||
// @Description Retrieve snapshot of the cluster DB
|
||||
// @Tags v16.?.?
|
||||
// @ID cluster-3-reallocation
|
||||
// @Produce json
|
||||
// @Param reallocations body api.ClusterProcessReallocate true "Process reallocations"
|
||||
// @Success 200 {string} string
|
||||
// @Failure 500 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/reallocation [put]
|
||||
func (h *ClusterHandler) Reallocation(c echo.Context) error {
|
||||
reallocations := []api.ClusterProcessReallocate{}
|
||||
|
||||
if err := util.ShouldBindJSONValidation(c, &reallocations, false); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
|
||||
}
|
||||
|
||||
for _, r := range reallocations {
|
||||
err := c.Validate(r)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
relocations := map[app.ProcessID]string{}
|
||||
|
||||
for _, r := range reallocations {
|
||||
for _, p := range r.Processes {
|
||||
relocations[app.ProcessID{
|
||||
ID: p.ID,
|
||||
Domain: p.Domain,
|
||||
}] = r.TargetNodeID
|
||||
}
|
||||
}
|
||||
|
||||
err := h.cluster.ProcessesRelocate("", relocations)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusInternalServerError, "", "%s", err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, "OK")
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// ListFiles lists all files on a filesystem
|
||||
// FilesystemListFiles lists all files on a filesystem
|
||||
// @Summary List all files on a filesystem
|
||||
// @Description List all files on a filesystem. The listing can be ordered by name, size, or date of last modification in ascending or descending order.
|
||||
// @Tags v16.?.?
|
||||
@ -23,13 +23,13 @@ import (
|
||||
// @Success 500 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/fs/{storage} [get]
|
||||
func (h *ClusterHandler) ListFiles(c echo.Context) error {
|
||||
func (h *ClusterHandler) FilesystemListFiles(c echo.Context) error {
|
||||
name := util.PathParam(c, "storage")
|
||||
pattern := util.DefaultQuery(c, "glob", "")
|
||||
sortby := util.DefaultQuery(c, "sort", "none")
|
||||
order := util.DefaultQuery(c, "order", "asc")
|
||||
|
||||
files := h.proxy.ListFiles(name, pattern)
|
||||
files := h.proxy.FilesystemList(name, pattern)
|
||||
|
||||
var sortFunc func(i, j int) bool
|
||||
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster/store"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
"github.com/datarhei/core/v16/http/handler/util"
|
||||
"github.com/datarhei/core/v16/iam/access"
|
||||
@ -23,7 +25,7 @@ import (
|
||||
// @Failure 403 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/iam/user [post]
|
||||
func (h *ClusterHandler) AddIdentity(c echo.Context) error {
|
||||
func (h *ClusterHandler) IAMIdentityAdd(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
superuser := util.DefaultContext(c, "superuser", false)
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
@ -50,18 +52,18 @@ func (h *ClusterHandler) AddIdentity(c echo.Context) error {
|
||||
return api.Err(http.StatusForbidden, "", "Only superusers can add superusers")
|
||||
}
|
||||
|
||||
if err := h.cluster.AddIdentity("", iamuser); err != nil {
|
||||
if err := h.cluster.IAMIdentityAdd("", iamuser); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "invalid identity: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := h.cluster.SetPolicies("", iamuser.Name, iampolicies); err != nil {
|
||||
if err := h.cluster.IAMPoliciesSet("", iamuser.Name, iampolicies); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "Invalid policies: %s", err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
// UpdateIdentity replaces an existing user
|
||||
// IAMIdentityUpdate replaces an existing user
|
||||
// @Summary Replace an existing user
|
||||
// @Description Replace an existing user.
|
||||
// @Tags v16.?.?
|
||||
@ -78,7 +80,7 @@ func (h *ClusterHandler) AddIdentity(c echo.Context) error {
|
||||
// @Failure 500 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/iam/user/{name} [put]
|
||||
func (h *ClusterHandler) UpdateIdentity(c echo.Context) error {
|
||||
func (h *ClusterHandler) IAMIdentityUpdate(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
superuser := util.DefaultContext(c, "superuser", false)
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
@ -128,13 +130,13 @@ func (h *ClusterHandler) UpdateIdentity(c echo.Context) error {
|
||||
}
|
||||
|
||||
if name != "$anon" {
|
||||
err = h.cluster.UpdateIdentity("", name, iamuser)
|
||||
err = h.cluster.IAMIdentityUpdate("", name, iamuser)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "%s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
err = h.cluster.SetPolicies("", iamuser.Name, iampolicies)
|
||||
err = h.cluster.IAMPoliciesSet("", iamuser.Name, iampolicies)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusInternalServerError, "", "set policies: %s", err.Error())
|
||||
}
|
||||
@ -142,7 +144,7 @@ func (h *ClusterHandler) UpdateIdentity(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
// UpdateIdentityPolicies replaces existing user policies
|
||||
// IAMIdentityUpdatePolicies replaces existing user policies
|
||||
// @Summary Replace policies of an user
|
||||
// @Description Replace policies of an user
|
||||
// @Tags v16.?.?
|
||||
@ -159,7 +161,7 @@ func (h *ClusterHandler) UpdateIdentity(c echo.Context) error {
|
||||
// @Failure 500 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/iam/user/{name}/policy [put]
|
||||
func (h *ClusterHandler) UpdateIdentityPolicies(c echo.Context) error {
|
||||
func (h *ClusterHandler) IAMIdentityUpdatePolicies(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
superuser := util.DefaultContext(c, "superuser", false)
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
@ -216,15 +218,18 @@ func (h *ClusterHandler) UpdateIdentityPolicies(c echo.Context) error {
|
||||
return api.Err(http.StatusForbidden, "", "only superusers can modify superusers")
|
||||
}
|
||||
|
||||
err = h.cluster.SetPolicies("", name, accessPolicies)
|
||||
err = h.cluster.IAMPoliciesSet("", name, accessPolicies)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
return api.Err(http.StatusNotFound, "", "set policies: %s", err.Error())
|
||||
}
|
||||
return api.Err(http.StatusInternalServerError, "", "set policies: %s", err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, policies)
|
||||
}
|
||||
|
||||
// ReloadIAM reloads the identities and policies from the cluster store to IAM
|
||||
// IAMReload reloads the identities and policies from the cluster store to IAM
|
||||
// @Summary Reload identities and policies
|
||||
// @Description Reload identities and policies
|
||||
// @Tags v16.?.?
|
||||
@ -234,7 +239,7 @@ func (h *ClusterHandler) UpdateIdentityPolicies(c echo.Context) error {
|
||||
// @Success 500 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/iam/reload [get]
|
||||
func (h *ClusterHandler) ReloadIAM(c echo.Context) error {
|
||||
func (h *ClusterHandler) IAMReload(c echo.Context) error {
|
||||
err := h.iam.ReloadIndentities()
|
||||
if err != nil {
|
||||
return api.Err(http.StatusInternalServerError, "", "reload identities: %w", err.Error())
|
||||
@ -248,7 +253,7 @@ func (h *ClusterHandler) ReloadIAM(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, "OK")
|
||||
}
|
||||
|
||||
// ListIdentities returns the list of identities stored in IAM
|
||||
// IAMIdentityList returns the list of identities stored in IAM
|
||||
// @Summary List of identities in IAM
|
||||
// @Description List of identities in IAM
|
||||
// @Tags v16.?.?
|
||||
@ -257,7 +262,7 @@ func (h *ClusterHandler) ReloadIAM(c echo.Context) error {
|
||||
// @Success 200 {array} api.IAMUser
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/iam/user [get]
|
||||
func (h *ClusterHandler) ListIdentities(c echo.Context) error {
|
||||
func (h *ClusterHandler) IAMIdentityList(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
|
||||
@ -292,7 +297,7 @@ func (h *ClusterHandler) ListIdentities(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, users)
|
||||
}
|
||||
|
||||
// ListIdentity returns the identity stored in IAM
|
||||
// IAMIdentityGet returns the identity stored in IAM
|
||||
// @Summary Identity in IAM
|
||||
// @Description Identity in IAM
|
||||
// @Tags v16.?.?
|
||||
@ -303,7 +308,7 @@ func (h *ClusterHandler) ListIdentities(c echo.Context) error {
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/iam/user/{name} [get]
|
||||
func (h *ClusterHandler) ListIdentity(c echo.Context) error {
|
||||
func (h *ClusterHandler) IAMIdentityGet(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
name := util.PathParam(c, "name")
|
||||
@ -342,7 +347,7 @@ func (h *ClusterHandler) ListIdentity(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
// ListPolicies returns the list of policies stored in IAM
|
||||
// IAMPolicyList returns the list of policies stored in IAM
|
||||
// @Summary List of policies in IAM
|
||||
// @Description List of policies IAM
|
||||
// @Tags v16.?.?
|
||||
@ -351,7 +356,7 @@ func (h *ClusterHandler) ListIdentity(c echo.Context) error {
|
||||
// @Success 200 {array} api.IAMPolicy
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/iam/policies [get]
|
||||
func (h *ClusterHandler) ListPolicies(c echo.Context) error {
|
||||
func (h *ClusterHandler) IAMPolicyList(c echo.Context) error {
|
||||
iampolicies := h.iam.ListPolicies("", "", nil, "", nil)
|
||||
|
||||
policies := []api.IAMPolicy{}
|
||||
@ -381,7 +386,7 @@ func (h *ClusterHandler) ListPolicies(c echo.Context) error {
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/iam/user/{name} [delete]
|
||||
func (h *ClusterHandler) RemoveIdentity(c echo.Context) error {
|
||||
func (h *ClusterHandler) IAMIdentityRemove(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
superuser := util.DefaultContext(c, "superuser", false)
|
||||
domain := util.DefaultQuery(c, "domain", "$none")
|
||||
@ -400,7 +405,7 @@ func (h *ClusterHandler) RemoveIdentity(c echo.Context) error {
|
||||
return api.Err(http.StatusForbidden, "", "Only superusers can remove superusers")
|
||||
}
|
||||
|
||||
if err := h.cluster.RemoveIdentity("", name); err != nil {
|
||||
if err := h.cluster.IAMIdentityRemove("", name); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "invalid identity: %s", err.Error())
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
clientapi "github.com/datarhei/core-client-go/v16/api"
|
||||
"github.com/datarhei/core/v16/cluster/proxy"
|
||||
"github.com/datarhei/core/v16/cluster"
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
"github.com/datarhei/core/v16/http/handler/util"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// GetNodes returns the list of proxy nodes in the cluster
|
||||
// NodeList returns the list of proxy nodes in the cluster
|
||||
// @Summary List of proxy nodes in the cluster
|
||||
// @Description List of proxy nodes in the cluster
|
||||
// @Tags v16.?.?
|
||||
@ -23,19 +24,27 @@ import (
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/node [get]
|
||||
func (h *ClusterHandler) GetNodes(c echo.Context) error {
|
||||
func (h *ClusterHandler) NodeList(c echo.Context) error {
|
||||
about, _ := h.cluster.About()
|
||||
|
||||
nodes := h.cluster.Store().NodeList()
|
||||
|
||||
list := []api.ClusterNode{}
|
||||
|
||||
for _, node := range about.Nodes {
|
||||
if dbnode, hasNode := nodes[node.ID]; hasNode {
|
||||
if dbnode.State == "maintenance" {
|
||||
node.State = dbnode.State
|
||||
}
|
||||
}
|
||||
|
||||
list = append(list, h.marshalClusterNode(node))
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, list)
|
||||
}
|
||||
|
||||
// GetNode returns the proxy node with the given ID
|
||||
// NodeGet returns the proxy node with the given ID
|
||||
// @Summary List a proxy node by its ID
|
||||
// @Description List a proxy node by its ID
|
||||
// @Tags v16.?.?
|
||||
@ -46,23 +55,31 @@ func (h *ClusterHandler) GetNodes(c echo.Context) error {
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/node/{id} [get]
|
||||
func (h *ClusterHandler) GetNode(c echo.Context) error {
|
||||
func (h *ClusterHandler) NodeGet(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
|
||||
about, _ := h.cluster.About()
|
||||
|
||||
nodes := h.cluster.Store().NodeList()
|
||||
|
||||
for _, node := range about.Nodes {
|
||||
if node.ID != id {
|
||||
continue
|
||||
}
|
||||
|
||||
if dbnode, hasNode := nodes[node.ID]; hasNode {
|
||||
if dbnode.State == "maintenance" {
|
||||
node.State = dbnode.State
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, h.marshalClusterNode(node))
|
||||
}
|
||||
|
||||
return api.Err(http.StatusNotFound, "", "node not found")
|
||||
}
|
||||
|
||||
// GetNodeVersion returns the proxy node version with the given ID
|
||||
// NodeGetVersion returns the proxy node version with the given ID
|
||||
// @Summary List a proxy node by its ID
|
||||
// @Description List a proxy node by its ID
|
||||
// @Tags v16.?.?
|
||||
@ -73,29 +90,29 @@ func (h *ClusterHandler) GetNode(c echo.Context) error {
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/node/{id}/version [get]
|
||||
func (h *ClusterHandler) GetNodeVersion(c echo.Context) error {
|
||||
func (h *ClusterHandler) NodeGetVersion(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
|
||||
peer, err := h.proxy.GetNodeReader(id)
|
||||
peer, err := h.proxy.NodeGet(id)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "node not found: %s", err.Error())
|
||||
}
|
||||
|
||||
v := peer.Version()
|
||||
v := peer.CoreAbout()
|
||||
|
||||
version := api.Version{
|
||||
Number: v.Number,
|
||||
Commit: v.Commit,
|
||||
Branch: v.Branch,
|
||||
Build: v.Build.Format(time.RFC3339),
|
||||
Arch: v.Arch,
|
||||
Compiler: v.Compiler,
|
||||
Number: v.Version.Number,
|
||||
Commit: v.Version.Commit,
|
||||
Branch: v.Version.Branch,
|
||||
Build: v.Version.Build.Format(time.RFC3339),
|
||||
Arch: v.Version.Arch,
|
||||
Compiler: v.Version.Compiler,
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, version)
|
||||
}
|
||||
|
||||
// GetNodeResources returns the resources from the proxy node with the given ID
|
||||
// NodeGetMedia returns the resources from the proxy node with the given ID
|
||||
// @Summary List the resources of a proxy node by its ID
|
||||
// @Description List the resources of a proxy node by its ID
|
||||
// @Tags v16.?.?
|
||||
@ -106,10 +123,10 @@ func (h *ClusterHandler) GetNodeVersion(c echo.Context) error {
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/node/{id}/files [get]
|
||||
func (h *ClusterHandler) GetNodeResources(c echo.Context) error {
|
||||
func (h *ClusterHandler) NodeGetMedia(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
|
||||
peer, err := h.proxy.GetNodeReader(id)
|
||||
peer, err := h.proxy.NodeGet(id)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "node not found: %s", err.Error())
|
||||
}
|
||||
@ -118,7 +135,7 @@ func (h *ClusterHandler) GetNodeResources(c echo.Context) error {
|
||||
Files: make(map[string][]string),
|
||||
}
|
||||
|
||||
peerFiles := peer.ListResources()
|
||||
peerFiles := peer.Core().MediaList()
|
||||
|
||||
files.LastUpdate = peerFiles.LastUpdate.Unix()
|
||||
|
||||
@ -158,12 +175,12 @@ func (h *ClusterHandler) NodeFSListFiles(c echo.Context) error {
|
||||
sortby := util.DefaultQuery(c, "sort", "none")
|
||||
order := util.DefaultQuery(c, "order", "asc")
|
||||
|
||||
peer, err := h.proxy.GetNodeReader(id)
|
||||
peer, err := h.proxy.NodeGet(id)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "node not found: %s", err.Error())
|
||||
}
|
||||
|
||||
files, err := peer.ListFiles(name, pattern)
|
||||
files, err := peer.Core().FilesystemList(name, pattern)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusInternalServerError, "", "retrieving file list: %s", err.Error())
|
||||
}
|
||||
@ -216,12 +233,12 @@ func (h *ClusterHandler) NodeFSGetFile(c echo.Context) error {
|
||||
storage := util.PathParam(c, "storage")
|
||||
path := util.PathWildcardParam(c)
|
||||
|
||||
peer, err := h.proxy.GetNodeReader(id)
|
||||
peer, err := h.proxy.NodeGet(id)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "node not found: %s", err.Error())
|
||||
}
|
||||
|
||||
file, err := peer.GetFile(storage, path, 0)
|
||||
file, err := peer.Core().FilesystemGetFile(storage, path, 0)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "%s", err.Error())
|
||||
}
|
||||
@ -252,14 +269,14 @@ func (h *ClusterHandler) NodeFSPutFile(c echo.Context) error {
|
||||
storage := util.PathParam(c, "storage")
|
||||
path := util.PathWildcardParam(c)
|
||||
|
||||
peer, err := h.proxy.GetNodeReader(id)
|
||||
peer, err := h.proxy.NodeGet(id)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "node not found: %s", err.Error())
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
|
||||
err = peer.PutFile(storage, path, req.Body)
|
||||
err = peer.Core().FilesystemPutFile(storage, path, req.Body)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "%s", err.Error())
|
||||
}
|
||||
@ -285,12 +302,12 @@ func (h *ClusterHandler) NodeFSDeleteFile(c echo.Context) error {
|
||||
storage := util.PathParam(c, "storage")
|
||||
path := util.PathWildcardParam(c)
|
||||
|
||||
peer, err := h.proxy.GetNodeReader(id)
|
||||
peer, err := h.proxy.NodeGet(id)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "node not found: %s", err.Error())
|
||||
}
|
||||
|
||||
err = peer.DeleteFile(storage, path)
|
||||
err = peer.Core().FilesystemDeleteFile(storage, path)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "%s", err.Error())
|
||||
}
|
||||
@ -298,7 +315,7 @@ func (h *ClusterHandler) NodeFSDeleteFile(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, nil)
|
||||
}
|
||||
|
||||
// ListNodeProcesses returns the list of processes running on a node of the cluster
|
||||
// NodeListProcesses returns the list of processes running on a node of the cluster
|
||||
// @Summary List of processes in the cluster on a node
|
||||
// @Description List of processes in the cluster on a node
|
||||
// @Tags v16.?.?
|
||||
@ -318,7 +335,7 @@ func (h *ClusterHandler) NodeFSDeleteFile(c echo.Context) error {
|
||||
// @Failure 500 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/node/{id}/process [get]
|
||||
func (h *ClusterHandler) ListNodeProcesses(c echo.Context) error {
|
||||
func (h *ClusterHandler) NodeListProcesses(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
filter := strings.FieldsFunc(util.DefaultQuery(c, "filter", ""), func(r rune) bool {
|
||||
@ -334,12 +351,12 @@ func (h *ClusterHandler) ListNodeProcesses(c echo.Context) error {
|
||||
ownerpattern := util.DefaultQuery(c, "ownerpattern", "")
|
||||
domainpattern := util.DefaultQuery(c, "domainpattern", "")
|
||||
|
||||
peer, err := h.proxy.GetNodeReader(id)
|
||||
peer, err := h.proxy.NodeGet(id)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "node not found: %s", err.Error())
|
||||
}
|
||||
|
||||
procs, err := peer.ProcessList(proxy.ProcessListOptions{
|
||||
procs, err := peer.Core().ProcessList(node.ProcessListOptions{
|
||||
ID: wantids,
|
||||
Filter: filter,
|
||||
Domain: domain,
|
||||
@ -353,7 +370,7 @@ func (h *ClusterHandler) ListNodeProcesses(c echo.Context) error {
|
||||
return api.Err(http.StatusInternalServerError, "", "node not available: %s", err.Error())
|
||||
}
|
||||
|
||||
processes := []clientapi.Process{}
|
||||
processes := []api.Process{}
|
||||
|
||||
for _, p := range procs {
|
||||
if !h.iam.Enforce(ctxuser, domain, "process", p.Config.ID, "read") {
|
||||
@ -365,3 +382,108 @@ func (h *ClusterHandler) ListNodeProcesses(c echo.Context) error {
|
||||
|
||||
return c.JSON(http.StatusOK, processes)
|
||||
}
|
||||
|
||||
// NodeGetState returns the state of a node with the given ID
|
||||
// @Summary Get the state of a node with the given ID
|
||||
// @Description Get the state of a node with the given ID
|
||||
// @Tags v16.?.?
|
||||
// @ID cluster-3-get-node-state
|
||||
// @Produce json
|
||||
// @Param id path string true "Node ID"
|
||||
// @Success 200 {object} api.ClusterNodeState
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/node/{id}/state [get]
|
||||
func (h *ClusterHandler) NodeGetState(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
|
||||
about, _ := h.cluster.About()
|
||||
|
||||
state := ""
|
||||
for _, node := range about.Nodes {
|
||||
if node.ID != id {
|
||||
continue
|
||||
}
|
||||
|
||||
state = node.State
|
||||
break
|
||||
}
|
||||
|
||||
if len(state) == 0 {
|
||||
return api.Err(http.StatusNotFound, "", "node not found")
|
||||
}
|
||||
|
||||
nodes := h.cluster.Store().NodeList()
|
||||
if node, hasNode := nodes[id]; hasNode {
|
||||
if node.State == "maintenance" {
|
||||
state = node.State
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, api.ClusterNodeState{
|
||||
State: state,
|
||||
})
|
||||
}
|
||||
|
||||
// NodeSetState sets the state of a node with the given ID
|
||||
// @Summary Set the state of a node with the given ID
|
||||
// @Description Set the state of a node with the given ID
|
||||
// @Tags v16.?.?
|
||||
// @ID cluster-3-set-node-state
|
||||
// @Produce json
|
||||
// @Param id path string true "Node ID"
|
||||
// @Param config body api.ClusterNodeState true "State"
|
||||
// @Success 200 {string} string
|
||||
// @Failure 400 {object} api.Error
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Failure 500 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/node/{id}/state [put]
|
||||
func (h *ClusterHandler) NodeSetState(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
|
||||
about, _ := h.cluster.About()
|
||||
|
||||
found := false
|
||||
for _, node := range about.Nodes {
|
||||
if node.ID != id {
|
||||
continue
|
||||
}
|
||||
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
if !found {
|
||||
return api.Err(http.StatusNotFound, "", "node not found")
|
||||
}
|
||||
|
||||
state := api.ClusterNodeState{}
|
||||
|
||||
if err := util.ShouldBindJSON(c, &state); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
|
||||
}
|
||||
|
||||
if state.State == "leave" {
|
||||
err := h.cluster.Leave("", id)
|
||||
if err != nil {
|
||||
if errors.Is(err, cluster.ErrUnknownNode) {
|
||||
return api.Err(http.StatusNotFound, "", "node not found")
|
||||
}
|
||||
|
||||
return api.Err(http.StatusInternalServerError, "", "%s", err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, "OK")
|
||||
}
|
||||
|
||||
err := h.cluster.NodeSetState("", id, state.State)
|
||||
if err != nil {
|
||||
if errors.Is(err, cluster.ErrUnsupportedNodeState) {
|
||||
return api.Err(http.StatusBadRequest, "", "%s", err.Error())
|
||||
}
|
||||
return api.Err(http.StatusInternalServerError, "", "%s", err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, "OK")
|
||||
}
|
||||
|
||||
@ -7,8 +7,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
clientapi "github.com/datarhei/core-client-go/v16/api"
|
||||
"github.com/datarhei/core/v16/cluster/proxy"
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
"github.com/datarhei/core/v16/cluster/store"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/glob"
|
||||
@ -20,7 +19,7 @@ import (
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
)
|
||||
|
||||
// GetAllProcesses returns the list of processes running on all nodes of the cluster
|
||||
// ProcessList returns the list of processes running on all nodes of the cluster
|
||||
// @Summary List of processes in the cluster
|
||||
// @Description List of processes in the cluster
|
||||
// @Tags v16.?.?
|
||||
@ -37,7 +36,7 @@ import (
|
||||
// @Success 200 {array} api.Process
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process [get]
|
||||
func (h *ClusterHandler) GetAllProcesses(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessList(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
filter := newFilter(util.DefaultQuery(c, "filter", ""))
|
||||
reference := util.DefaultQuery(c, "reference", "")
|
||||
@ -50,7 +49,7 @@ func (h *ClusterHandler) GetAllProcesses(c echo.Context) error {
|
||||
ownerpattern := util.DefaultQuery(c, "ownerpattern", "")
|
||||
domainpattern := util.DefaultQuery(c, "domainpattern", "")
|
||||
|
||||
procs := h.proxy.ListProcesses(proxy.ProcessListOptions{
|
||||
procs := h.proxy.ProcessList(node.ProcessListOptions{
|
||||
ID: wantids,
|
||||
Filter: filter.Slice(),
|
||||
Domain: domain,
|
||||
@ -61,7 +60,7 @@ func (h *ClusterHandler) GetAllProcesses(c echo.Context) error {
|
||||
DomainPattern: domainpattern,
|
||||
})
|
||||
|
||||
processes := []clientapi.Process{}
|
||||
processes := []api.Process{}
|
||||
pmap := map[app.ProcessID]struct{}{}
|
||||
|
||||
for _, p := range procs {
|
||||
@ -77,7 +76,7 @@ func (h *ClusterHandler) GetAllProcesses(c echo.Context) error {
|
||||
|
||||
// Here we have to add those processes that are in the cluster DB and couldn't be deployed
|
||||
{
|
||||
processes := h.cluster.ListProcesses()
|
||||
processes := h.cluster.Store().ProcessList()
|
||||
filtered := h.getFilteredStoreProcesses(processes, wantids, domain, reference, idpattern, refpattern, ownerpattern, domainpattern)
|
||||
|
||||
for _, p := range filtered {
|
||||
@ -139,7 +138,7 @@ func (h *ClusterHandler) GetAllProcesses(c echo.Context) error {
|
||||
return c.Stream(http.StatusOK, "application/json", buf)
|
||||
}
|
||||
|
||||
func (h *ClusterHandler) getFilteredStoreProcesses(processes []store.Process, wantids []string, domain, reference, idpattern, refpattern, ownerpattern, domainpattern string) []store.Process {
|
||||
func (h *ClusterHandler) getFilteredStoreProcesses(processes []store.Process, wantids []string, _, reference, idpattern, refpattern, ownerpattern, domainpattern string) []store.Process {
|
||||
filtered := []store.Process{}
|
||||
|
||||
count := 0
|
||||
@ -293,7 +292,7 @@ func (h *ClusterHandler) convertStoreProcessToAPIProcess(p store.Process, filter
|
||||
return process
|
||||
}
|
||||
|
||||
// GetProcess returns the process with the given ID whereever it's running on the cluster
|
||||
// ProcessGet returns the process with the given ID whereever it's running on the cluster
|
||||
// @Summary List a process by its ID
|
||||
// @Description List a process by its ID. Use the filter parameter to specifiy the level of detail of the output.
|
||||
// @Tags v16.?.?
|
||||
@ -307,7 +306,7 @@ func (h *ClusterHandler) convertStoreProcessToAPIProcess(p store.Process, filter
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process/{id} [get]
|
||||
func (h *ClusterHandler) GetProcess(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessGet(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
id := util.PathParam(c, "id")
|
||||
filter := newFilter(util.DefaultQuery(c, "filter", ""))
|
||||
@ -317,7 +316,7 @@ func (h *ClusterHandler) GetProcess(c echo.Context) error {
|
||||
return api.Err(http.StatusForbidden, "")
|
||||
}
|
||||
|
||||
procs := h.proxy.ListProcesses(proxy.ProcessListOptions{
|
||||
procs := h.proxy.ProcessList(node.ProcessListOptions{
|
||||
ID: []string{id},
|
||||
Filter: filter.Slice(),
|
||||
Domain: domain,
|
||||
@ -325,7 +324,7 @@ func (h *ClusterHandler) GetProcess(c echo.Context) error {
|
||||
|
||||
if len(procs) == 0 {
|
||||
// Check the store in the cluster for an undeployed process
|
||||
p, err := h.cluster.GetProcess(app.NewProcessID(id, domain))
|
||||
p, err := h.cluster.Store().ProcessGet(app.NewProcessID(id, domain))
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "Unknown process ID: %s", id)
|
||||
}
|
||||
@ -355,7 +354,7 @@ func (h *ClusterHandler) GetProcess(c echo.Context) error {
|
||||
// @Failure 403 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process [post]
|
||||
func (h *ClusterHandler) AddProcess(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessAdd(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
superuser := util.DefaultContext(c, "superuser", false)
|
||||
|
||||
@ -390,12 +389,12 @@ func (h *ClusterHandler) AddProcess(c echo.Context) error {
|
||||
|
||||
config, metadata := process.Marshal()
|
||||
|
||||
if err := h.cluster.AddProcess("", config); err != nil {
|
||||
if err := h.cluster.ProcessAdd("", config); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "adding process config: %s", err.Error())
|
||||
}
|
||||
|
||||
for key, value := range metadata {
|
||||
h.cluster.SetProcessMetadata("", config.ProcessID(), key, value)
|
||||
h.cluster.ProcessSetMetadata("", config.ProcessID(), key, value)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, process)
|
||||
@ -417,7 +416,7 @@ func (h *ClusterHandler) AddProcess(c echo.Context) error {
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process/{id} [put]
|
||||
func (h *ClusterHandler) UpdateProcess(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessUpdate(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
superuser := util.DefaultContext(c, "superuser", false)
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
@ -437,7 +436,7 @@ func (h *ClusterHandler) UpdateProcess(c echo.Context) error {
|
||||
|
||||
pid := process.ProcessID()
|
||||
|
||||
current, err := h.cluster.GetProcess(pid)
|
||||
current, err := h.cluster.Store().ProcessGet(pid)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "process not found: %s in domain '%s'", pid.ID, pid.Domain)
|
||||
}
|
||||
@ -461,7 +460,7 @@ func (h *ClusterHandler) UpdateProcess(c echo.Context) error {
|
||||
|
||||
config, metadata := process.Marshal()
|
||||
|
||||
if err := h.cluster.UpdateProcess("", pid, config); err != nil {
|
||||
if err := h.cluster.ProcessUpdate("", pid, config); err != nil {
|
||||
if err == restream.ErrUnknownProcess {
|
||||
return api.Err(http.StatusNotFound, "", "process not found: %s in domain '%s'", pid.ID, pid.Domain)
|
||||
}
|
||||
@ -472,7 +471,7 @@ func (h *ClusterHandler) UpdateProcess(c echo.Context) error {
|
||||
pid = process.ProcessID()
|
||||
|
||||
for key, value := range metadata {
|
||||
h.cluster.SetProcessMetadata("", pid, key, value)
|
||||
h.cluster.ProcessSetMetadata("", pid, key, value)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, process)
|
||||
@ -494,7 +493,7 @@ func (h *ClusterHandler) UpdateProcess(c echo.Context) error {
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process/{id}/command [put]
|
||||
func (h *ClusterHandler) SetProcessCommand(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessSetCommand(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
@ -523,14 +522,14 @@ func (h *ClusterHandler) SetProcessCommand(c echo.Context) error {
|
||||
return api.Err(http.StatusBadRequest, "", "unknown command provided. known commands are: start, stop, reload, restart")
|
||||
}
|
||||
|
||||
if err := h.cluster.SetProcessCommand("", pid, command.Command); err != nil {
|
||||
if err := h.cluster.ProcessSetCommand("", pid, command.Command); err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "command failed: %s", err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, "OK")
|
||||
}
|
||||
|
||||
// SetProcessMetadata stores metadata with a process
|
||||
// ProcessSetMetadata stores metadata with a process
|
||||
// @Summary Add JSON metadata with a process under the given key
|
||||
// @Description Add arbitrary JSON metadata under the given key. If the key exists, all already stored metadata with this key will be overwritten. If the key doesn't exist, it will be created.
|
||||
// @Tags v16.?.?
|
||||
@ -546,7 +545,7 @@ func (h *ClusterHandler) SetProcessCommand(c echo.Context) error {
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process/{id}/metadata/{key} [put]
|
||||
func (h *ClusterHandler) SetProcessMetadata(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessSetMetadata(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
key := util.PathParam(c, "key")
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
@ -571,14 +570,14 @@ func (h *ClusterHandler) SetProcessMetadata(c echo.Context) error {
|
||||
Domain: domain,
|
||||
}
|
||||
|
||||
if err := h.cluster.SetProcessMetadata("", pid, key, data); err != nil {
|
||||
if err := h.cluster.ProcessSetMetadata("", pid, key, data); err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "setting metadata failed: %s", err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// GetProcessMetadata returns the metadata stored with a process
|
||||
// ProcessGetMetadata returns the metadata stored with a process
|
||||
// @Summary Retrieve JSON metadata stored with a process under a key
|
||||
// @Description Retrieve the previously stored JSON metadata under the given key. If the key is empty, all metadata will be returned.
|
||||
// @Tags v16.?.?
|
||||
@ -593,7 +592,7 @@ func (h *ClusterHandler) SetProcessMetadata(c echo.Context) error {
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process/{id}/metadata/{key} [get]
|
||||
func (h *ClusterHandler) GetProcessMetadata(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessGetMetadata(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
key := util.PathParam(c, "key")
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
@ -608,7 +607,7 @@ func (h *ClusterHandler) GetProcessMetadata(c echo.Context) error {
|
||||
Domain: domain,
|
||||
}
|
||||
|
||||
data, err := h.cluster.GetProcessMetadata("", pid, key)
|
||||
data, err := h.cluster.ProcessGetMetadata("", pid, key)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "unknown process ID: %s", err.Error())
|
||||
}
|
||||
@ -628,7 +627,7 @@ func (h *ClusterHandler) GetProcessMetadata(c echo.Context) error {
|
||||
// @Failure 403 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process/{id}/probe [get]
|
||||
func (h *ClusterHandler) ProbeProcess(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessProbe(c echo.Context) error {
|
||||
id := util.PathParam(c, "id")
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
@ -642,14 +641,14 @@ func (h *ClusterHandler) ProbeProcess(c echo.Context) error {
|
||||
Domain: domain,
|
||||
}
|
||||
|
||||
nodeid, err := h.proxy.FindNodeFromProcess(pid)
|
||||
nodeid, err := h.proxy.ProcessFindNodeID(pid)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusOK, api.Probe{
|
||||
Log: []string{fmt.Sprintf("the process can't be found: %s", err.Error())},
|
||||
})
|
||||
}
|
||||
|
||||
probe, _ := h.proxy.ProbeProcess(nodeid, pid)
|
||||
probe, _ := h.proxy.ProcessProbe(nodeid, pid)
|
||||
|
||||
return c.JSON(http.StatusOK, probe)
|
||||
}
|
||||
@ -669,7 +668,7 @@ func (h *ClusterHandler) ProbeProcess(c echo.Context) error {
|
||||
// @Failure 500 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process/probe [post]
|
||||
func (h *ClusterHandler) ProbeProcessConfig(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessProbeConfig(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
coreid := util.DefaultQuery(c, "coreid", "")
|
||||
|
||||
@ -702,12 +701,12 @@ func (h *ClusterHandler) ProbeProcessConfig(c echo.Context) error {
|
||||
|
||||
config, _ := process.Marshal()
|
||||
|
||||
coreid = h.proxy.FindNodeFromResources(coreid, config.LimitCPU, config.LimitMemory)
|
||||
coreid = h.proxy.FindNodeForResources(coreid, config.LimitCPU, config.LimitMemory)
|
||||
if len(coreid) == 0 {
|
||||
return api.Err(http.StatusInternalServerError, "", "Not enough available resources available to execute probe")
|
||||
return api.Err(http.StatusInternalServerError, "", "Not enough resources available to execute probe")
|
||||
}
|
||||
|
||||
probe, _ := h.proxy.ProbeProcessConfig(coreid, config)
|
||||
probe, _ := h.proxy.ProcessProbeConfig(coreid, config)
|
||||
|
||||
return c.JSON(http.StatusOK, probe)
|
||||
}
|
||||
@ -724,7 +723,7 @@ func (h *ClusterHandler) ProbeProcessConfig(c echo.Context) error {
|
||||
// @Failure 403 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/process/{id} [delete]
|
||||
func (h *ClusterHandler) DeleteProcess(c echo.Context) error {
|
||||
func (h *ClusterHandler) ProcessDelete(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
id := util.PathParam(c, "id")
|
||||
@ -738,7 +737,7 @@ func (h *ClusterHandler) DeleteProcess(c echo.Context) error {
|
||||
Domain: domain,
|
||||
}
|
||||
|
||||
if err := h.cluster.RemoveProcess("", pid); err != nil {
|
||||
if err := h.cluster.ProcessRemove("", pid); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "", "%s", err.Error())
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// ListStoreProcesses returns the list of processes stored in the DB of the cluster
|
||||
// StoreListProcesses returns the list of processes stored in the DB of the cluster
|
||||
// @Summary List of processes in the cluster DB
|
||||
// @Description List of processes in the cluster DB
|
||||
// @Tags v16.?.?
|
||||
@ -21,10 +21,10 @@ import (
|
||||
// @Success 200 {array} api.Process
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/db/process [get]
|
||||
func (h *ClusterHandler) ListStoreProcesses(c echo.Context) error {
|
||||
func (h *ClusterHandler) StoreListProcesses(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
|
||||
procs := h.cluster.ListProcesses()
|
||||
procs := h.cluster.Store().ProcessList()
|
||||
|
||||
processes := []api.Process{}
|
||||
|
||||
@ -52,7 +52,7 @@ func (h *ClusterHandler) ListStoreProcesses(c echo.Context) error {
|
||||
// @Success 200 {object} api.Process
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/db/process/:id [get]
|
||||
func (h *ClusterHandler) GetStoreProcess(c echo.Context) error {
|
||||
func (h *ClusterHandler) StoreGetProcess(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
id := util.PathParam(c, "id")
|
||||
@ -66,7 +66,7 @@ func (h *ClusterHandler) GetStoreProcess(c echo.Context) error {
|
||||
return api.Err(http.StatusForbidden, "", "API user %s is not allowed to read this process", ctxuser)
|
||||
}
|
||||
|
||||
p, err := h.cluster.GetProcess(pid)
|
||||
p, err := h.cluster.Store().ProcessGet(pid)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "process not found: %s in domain '%s'", pid.ID, pid.Domain)
|
||||
}
|
||||
@ -76,7 +76,7 @@ func (h *ClusterHandler) GetStoreProcess(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, process)
|
||||
}
|
||||
|
||||
// GetStoreProcessNodeMap returns a map of which process is running on which node
|
||||
// StoreGetProcessNodeMap returns a map of which process is running on which node
|
||||
// @Summary Retrieve a map of which process is running on which node
|
||||
// @Description Retrieve a map of which process is running on which node
|
||||
// @Tags v16.?.?
|
||||
@ -85,13 +85,13 @@ func (h *ClusterHandler) GetStoreProcess(c echo.Context) error {
|
||||
// @Success 200 {object} api.ClusterProcessMap
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/map/process [get]
|
||||
func (h *ClusterHandler) GetStoreProcessNodeMap(c echo.Context) error {
|
||||
m := h.cluster.GetProcessNodeMap()
|
||||
func (h *ClusterHandler) StoreGetProcessNodeMap(c echo.Context) error {
|
||||
m := h.cluster.Store().ProcessGetNodeMap()
|
||||
|
||||
return c.JSON(http.StatusOK, m)
|
||||
}
|
||||
|
||||
// ListStoreIdentities returns the list of identities stored in the DB of the cluster
|
||||
// StoreListIdentities returns the list of identities stored in the DB of the cluster
|
||||
// @Summary List of identities in the cluster
|
||||
// @Description List of identities in the cluster
|
||||
// @Tags v16.?.?
|
||||
@ -100,15 +100,15 @@ func (h *ClusterHandler) GetStoreProcessNodeMap(c echo.Context) error {
|
||||
// @Success 200 {array} api.IAMUser
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/db/user [get]
|
||||
func (h *ClusterHandler) ListStoreIdentities(c echo.Context) error {
|
||||
func (h *ClusterHandler) StoreListIdentities(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
|
||||
updatedAt, identities := h.cluster.ListIdentities()
|
||||
identities := h.cluster.Store().IAMIdentityList()
|
||||
|
||||
users := make([]api.IAMUser, len(identities))
|
||||
users := make([]api.IAMUser, len(identities.Users))
|
||||
|
||||
for i, iamuser := range identities {
|
||||
for i, iamuser := range identities.Users {
|
||||
if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "read") {
|
||||
continue
|
||||
}
|
||||
@ -119,27 +119,27 @@ func (h *ClusterHandler) ListStoreIdentities(c echo.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, policies := h.cluster.ListUserPolicies(iamuser.Name)
|
||||
users[i].Marshal(iamuser, policies)
|
||||
policies := h.cluster.Store().IAMIdentityPolicyList(iamuser.Name)
|
||||
users[i].Marshal(iamuser, policies.Policies)
|
||||
}
|
||||
|
||||
c.Response().Header().Set("Last-Modified", updatedAt.UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
||||
c.Response().Header().Set("Last-Modified", identities.UpdatedAt.UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
||||
|
||||
return c.JSON(http.StatusOK, users)
|
||||
}
|
||||
|
||||
// ListStoreIdentity returns the list of identities stored in the DB of the cluster
|
||||
// StoreGetIdentity returns the list of identities stored in the DB of the cluster
|
||||
// @Summary List of identities in the cluster
|
||||
// @Description List of identities in the cluster
|
||||
// @Tags v16.?.?
|
||||
// @ID cluster-3-db-list-identity
|
||||
// @ID cluster-3-db-get-identity
|
||||
// @Produce json
|
||||
// @Success 200 {object} api.IAMUser
|
||||
// @Failure 403 {object} api.Error
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/db/user/{name} [get]
|
||||
func (h *ClusterHandler) ListStoreIdentity(c echo.Context) error {
|
||||
func (h *ClusterHandler) StoreGetIdentity(c echo.Context) error {
|
||||
ctxuser := util.DefaultContext(c, "user", "")
|
||||
domain := util.DefaultQuery(c, "domain", "")
|
||||
name := util.PathParam(c, "name")
|
||||
@ -150,14 +150,15 @@ func (h *ClusterHandler) ListStoreIdentity(c echo.Context) error {
|
||||
|
||||
var updatedAt time.Time
|
||||
var iamuser identity.User
|
||||
var err error
|
||||
|
||||
if name != "$anon" {
|
||||
updatedAt, iamuser, err = h.cluster.ListIdentity(name)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusNotFound, "", "%s", err.Error())
|
||||
user := h.cluster.Store().IAMIdentityGet(name)
|
||||
if len(user.Users) == 0 {
|
||||
return api.Err(http.StatusNotFound, "")
|
||||
}
|
||||
|
||||
updatedAt, iamuser = user.UpdatedAt, user.Users[0]
|
||||
|
||||
if ctxuser != iamuser.Name {
|
||||
if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") {
|
||||
iamuser = identity.User{
|
||||
@ -171,20 +172,20 @@ func (h *ClusterHandler) ListStoreIdentity(c echo.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
policiesUpdatedAt, policies := h.cluster.ListUserPolicies(name)
|
||||
policies := h.cluster.Store().IAMIdentityPolicyList(name)
|
||||
if updatedAt.IsZero() {
|
||||
updatedAt = policiesUpdatedAt
|
||||
updatedAt = policies.UpdatedAt
|
||||
}
|
||||
|
||||
user := api.IAMUser{}
|
||||
user.Marshal(iamuser, policies)
|
||||
user.Marshal(iamuser, policies.Policies)
|
||||
|
||||
c.Response().Header().Set("Last-Modified", updatedAt.UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
||||
|
||||
return c.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
// ListStorePolicies returns the list of policies stored in the DB of the cluster
|
||||
// StoreListPolicies returns the list of policies stored in the DB of the cluster
|
||||
// @Summary List of policies in the cluster
|
||||
// @Description List of policies in the cluster
|
||||
// @Tags v16.?.?
|
||||
@ -193,26 +194,27 @@ func (h *ClusterHandler) ListStoreIdentity(c echo.Context) error {
|
||||
// @Success 200 {array} api.IAMPolicy
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/db/policies [get]
|
||||
func (h *ClusterHandler) ListStorePolicies(c echo.Context) error {
|
||||
updatedAt, clusterpolicies := h.cluster.ListPolicies()
|
||||
func (h *ClusterHandler) StoreListPolicies(c echo.Context) error {
|
||||
clusterpolicies := h.cluster.Store().IAMPolicyList()
|
||||
|
||||
policies := []api.IAMPolicy{}
|
||||
|
||||
for _, pol := range clusterpolicies {
|
||||
for _, pol := range clusterpolicies.Policies {
|
||||
policies = append(policies, api.IAMPolicy{
|
||||
Name: pol.Name,
|
||||
Domain: pol.Domain,
|
||||
Resource: pol.Resource,
|
||||
Types: pol.Types,
|
||||
Actions: pol.Actions,
|
||||
})
|
||||
}
|
||||
|
||||
c.Response().Header().Set("Last-Modified", updatedAt.UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
||||
c.Response().Header().Set("Last-Modified", clusterpolicies.UpdatedAt.UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
||||
|
||||
return c.JSON(http.StatusOK, policies)
|
||||
}
|
||||
|
||||
// ListStoreLocks returns the list of currently stored locks
|
||||
// StoreListLocks returns the list of currently stored locks
|
||||
// @Summary List locks in the cluster DB
|
||||
// @Description List of locks in the cluster DB
|
||||
// @Tags v16.?.?
|
||||
@ -221,8 +223,8 @@ func (h *ClusterHandler) ListStorePolicies(c echo.Context) error {
|
||||
// @Success 200 {array} api.ClusterLock
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/db/locks [get]
|
||||
func (h *ClusterHandler) ListStoreLocks(c echo.Context) error {
|
||||
clusterlocks := h.cluster.ListLocks()
|
||||
func (h *ClusterHandler) StoreListLocks(c echo.Context) error {
|
||||
clusterlocks := h.cluster.Store().LockList()
|
||||
|
||||
locks := []api.ClusterLock{}
|
||||
|
||||
@ -236,7 +238,7 @@ func (h *ClusterHandler) ListStoreLocks(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, locks)
|
||||
}
|
||||
|
||||
// ListStoreKV returns the list of currently stored key/value pairs
|
||||
// StoreListKV returns the list of currently stored key/value pairs
|
||||
// @Summary List KV in the cluster DB
|
||||
// @Description List of KV in the cluster DB
|
||||
// @Tags v16.?.?
|
||||
@ -245,8 +247,8 @@ func (h *ClusterHandler) ListStoreLocks(c echo.Context) error {
|
||||
// @Success 200 {object} api.ClusterKVS
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/db/kv [get]
|
||||
func (h *ClusterHandler) ListStoreKV(c echo.Context) error {
|
||||
clusterkv := h.cluster.ListKV("")
|
||||
func (h *ClusterHandler) StoreListKV(c echo.Context) error {
|
||||
clusterkv := h.cluster.Store().KVSList("")
|
||||
|
||||
kvs := api.ClusterKVS{}
|
||||
|
||||
@ -259,3 +261,28 @@ func (h *ClusterHandler) ListStoreKV(c echo.Context) error {
|
||||
|
||||
return c.JSON(http.StatusOK, kvs)
|
||||
}
|
||||
|
||||
// StoreListNodes returns the list of stored node metadata
|
||||
// @Summary List nodes in the cluster DB
|
||||
// @Description List of nodes in the cluster DB
|
||||
// @Tags v16.?.?
|
||||
// @ID cluster-3-db-list-nodes
|
||||
// @Produce json
|
||||
// @Success 200 {array} api.ClusterStoreNode
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/cluster/db/node [get]
|
||||
func (h *ClusterHandler) StoreListNodes(c echo.Context) error {
|
||||
clusternodes := h.cluster.Store().NodeList()
|
||||
|
||||
nodes := []api.ClusterStoreNode{}
|
||||
|
||||
for nodeid, v := range clusternodes {
|
||||
nodes = append(nodes, api.ClusterStoreNode{
|
||||
ID: nodeid,
|
||||
State: v.State,
|
||||
UpdatedAt: v.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, nodes)
|
||||
}
|
||||
|
||||
@ -84,6 +84,7 @@ func (h *IAMHandler) AddIdentity(c echo.Context) error {
|
||||
// @Param name path string true "Username"
|
||||
// @Param domain query string false "Domain of the acting user"
|
||||
// @Success 200 {string} string
|
||||
// @Failure 400 {object} api.Error
|
||||
// @Failure 403 {object} api.Error
|
||||
// @Failure 404 {object} api.Error
|
||||
// @Failure 500 {object} api.Error
|
||||
|
||||
@ -206,7 +206,7 @@ func NewServer(config Config) (serverhandler.Server, error) {
|
||||
|
||||
if config.Cluster != nil {
|
||||
if httpfs.Filesystem.Type() == "disk" || httpfs.Filesystem.Type() == "mem" {
|
||||
httpfs.Filesystem = fs.NewClusterFS(httpfs.Filesystem.Name(), httpfs.Filesystem, config.Cluster.ProxyReader())
|
||||
httpfs.Filesystem = fs.NewClusterFS(httpfs.Filesystem.Name(), httpfs.Filesystem, config.Cluster.Manager())
|
||||
}
|
||||
}
|
||||
|
||||
@ -728,54 +728,59 @@ func (s *server) setRoutesV3(v3 *echo.Group) {
|
||||
|
||||
v3.GET("/cluster/snapshot", s.v3handler.cluster.GetSnapshot)
|
||||
|
||||
v3.GET("/cluster/db/process", s.v3handler.cluster.ListStoreProcesses)
|
||||
v3.GET("/cluster/db/process/:id", s.v3handler.cluster.GetStoreProcess)
|
||||
v3.GET("/cluster/db/user", s.v3handler.cluster.ListStoreIdentities)
|
||||
v3.GET("/cluster/db/user/:name", s.v3handler.cluster.ListStoreIdentity)
|
||||
v3.GET("/cluster/db/policies", s.v3handler.cluster.ListStorePolicies)
|
||||
v3.GET("/cluster/db/locks", s.v3handler.cluster.ListStoreLocks)
|
||||
v3.GET("/cluster/db/kv", s.v3handler.cluster.ListStoreKV)
|
||||
v3.GET("/cluster/db/map/process", s.v3handler.cluster.GetStoreProcessNodeMap)
|
||||
v3.GET("/cluster/db/process", s.v3handler.cluster.StoreListProcesses)
|
||||
v3.GET("/cluster/db/process/:id", s.v3handler.cluster.StoreGetProcess)
|
||||
v3.GET("/cluster/db/user", s.v3handler.cluster.StoreListIdentities)
|
||||
v3.GET("/cluster/db/user/:name", s.v3handler.cluster.StoreGetIdentity)
|
||||
v3.GET("/cluster/db/policies", s.v3handler.cluster.StoreListPolicies)
|
||||
v3.GET("/cluster/db/locks", s.v3handler.cluster.StoreListLocks)
|
||||
v3.GET("/cluster/db/kv", s.v3handler.cluster.StoreListKV)
|
||||
v3.GET("/cluster/db/map/process", s.v3handler.cluster.StoreGetProcessNodeMap)
|
||||
v3.GET("/cluster/db/node", s.v3handler.cluster.StoreListNodes)
|
||||
|
||||
v3.GET("/cluster/iam/user", s.v3handler.cluster.ListIdentities)
|
||||
v3.GET("/cluster/iam/user/:name", s.v3handler.cluster.ListIdentity)
|
||||
v3.GET("/cluster/iam/policies", s.v3handler.cluster.ListPolicies)
|
||||
v3.GET("/cluster/iam/user", s.v3handler.cluster.IAMIdentityList)
|
||||
v3.GET("/cluster/iam/user/:name", s.v3handler.cluster.IAMIdentityGet)
|
||||
v3.GET("/cluster/iam/policies", s.v3handler.cluster.IAMPolicyList)
|
||||
|
||||
v3.GET("/cluster/process", s.v3handler.cluster.GetAllProcesses)
|
||||
v3.GET("/cluster/process/:id", s.v3handler.cluster.GetProcess)
|
||||
v3.GET("/cluster/process/:id/metadata", s.v3handler.cluster.GetProcessMetadata)
|
||||
v3.GET("/cluster/process/:id/metadata/:key", s.v3handler.cluster.GetProcessMetadata)
|
||||
v3.GET("/cluster/process", s.v3handler.cluster.ProcessList)
|
||||
v3.GET("/cluster/process/:id", s.v3handler.cluster.ProcessGet)
|
||||
v3.GET("/cluster/process/:id/metadata", s.v3handler.cluster.ProcessGetMetadata)
|
||||
v3.GET("/cluster/process/:id/metadata/:key", s.v3handler.cluster.ProcessGetMetadata)
|
||||
|
||||
v3.GET("/cluster/node", s.v3handler.cluster.GetNodes)
|
||||
v3.GET("/cluster/node/:id", s.v3handler.cluster.GetNode)
|
||||
v3.GET("/cluster/node/:id/files", s.v3handler.cluster.GetNodeResources)
|
||||
v3.GET("/cluster/node", s.v3handler.cluster.NodeList)
|
||||
v3.GET("/cluster/node/:id", s.v3handler.cluster.NodeGet)
|
||||
v3.GET("/cluster/node/:id/files", s.v3handler.cluster.NodeGetMedia)
|
||||
v3.GET("/cluster/node/:id/fs/:storage", s.v3handler.cluster.NodeFSListFiles)
|
||||
v3.GET("/cluster/node/:id/fs/:storage/*", s.v3handler.cluster.NodeFSGetFile)
|
||||
v3.GET("/cluster/node/:id/process", s.v3handler.cluster.ListNodeProcesses)
|
||||
v3.GET("/cluster/node/:id/version", s.v3handler.cluster.GetNodeVersion)
|
||||
v3.GET("/cluster/node/:id/process", s.v3handler.cluster.NodeListProcesses)
|
||||
v3.GET("/cluster/node/:id/version", s.v3handler.cluster.NodeGetVersion)
|
||||
v3.GET("/cluster/node/:id/state", s.v3handler.cluster.NodeGetState)
|
||||
|
||||
v3.GET("/cluster/fs/:storage", s.v3handler.cluster.ListFiles)
|
||||
v3.GET("/cluster/fs/:storage", s.v3handler.cluster.FilesystemListFiles)
|
||||
|
||||
if !s.readOnly {
|
||||
v3.PUT("/cluster/transfer/:id", s.v3handler.cluster.TransferLeadership)
|
||||
v3.PUT("/cluster/leave", s.v3handler.cluster.Leave)
|
||||
|
||||
v3.POST("/cluster/process", s.v3handler.cluster.AddProcess)
|
||||
v3.POST("/cluster/process/probe", s.v3handler.cluster.ProbeProcessConfig)
|
||||
v3.PUT("/cluster/process/:id", s.v3handler.cluster.UpdateProcess)
|
||||
v3.GET("/cluster/process/:id/probe", s.v3handler.cluster.ProbeProcess)
|
||||
v3.DELETE("/cluster/process/:id", s.v3handler.cluster.DeleteProcess)
|
||||
v3.PUT("/cluster/process/:id/command", s.v3handler.cluster.SetProcessCommand)
|
||||
v3.PUT("/cluster/process/:id/metadata/:key", s.v3handler.cluster.SetProcessMetadata)
|
||||
v3.POST("/cluster/process", s.v3handler.cluster.ProcessAdd)
|
||||
v3.POST("/cluster/process/probe", s.v3handler.cluster.ProcessProbeConfig)
|
||||
v3.PUT("/cluster/process/:id", s.v3handler.cluster.ProcessUpdate)
|
||||
v3.GET("/cluster/process/:id/probe", s.v3handler.cluster.ProcessProbe)
|
||||
v3.DELETE("/cluster/process/:id", s.v3handler.cluster.ProcessDelete)
|
||||
v3.PUT("/cluster/process/:id/command", s.v3handler.cluster.ProcessSetCommand)
|
||||
v3.PUT("/cluster/process/:id/metadata/:key", s.v3handler.cluster.ProcessSetMetadata)
|
||||
|
||||
v3.PUT("/cluster/reallocation", s.v3handler.cluster.Reallocation)
|
||||
|
||||
v3.DELETE("/cluster/node/:id/fs/:storage/*", s.v3handler.cluster.NodeFSDeleteFile)
|
||||
v3.PUT("/cluster/node/:id/fs/:storage/*", s.v3handler.cluster.NodeFSPutFile)
|
||||
v3.PUT("/cluster/node/:id/state", s.v3handler.cluster.NodeSetState)
|
||||
|
||||
v3.PUT("/cluster/iam/reload", s.v3handler.cluster.ReloadIAM)
|
||||
v3.POST("/cluster/iam/user", s.v3handler.cluster.AddIdentity)
|
||||
v3.PUT("/cluster/iam/user/:name", s.v3handler.cluster.UpdateIdentity)
|
||||
v3.PUT("/cluster/iam/user/:name/policy", s.v3handler.cluster.UpdateIdentityPolicies)
|
||||
v3.DELETE("/cluster/iam/user/:name", s.v3handler.cluster.RemoveIdentity)
|
||||
v3.PUT("/cluster/iam/reload", s.v3handler.cluster.IAMReload)
|
||||
v3.POST("/cluster/iam/user", s.v3handler.cluster.IAMIdentityAdd)
|
||||
v3.PUT("/cluster/iam/user/:name", s.v3handler.cluster.IAMIdentityUpdate)
|
||||
v3.PUT("/cluster/iam/user/:name/policy", s.v3handler.cluster.IAMIdentityUpdatePolicies)
|
||||
v3.DELETE("/cluster/iam/user/:name", s.v3handler.cluster.IAMIdentityRemove)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -139,7 +139,7 @@ Output #0, hls, to './data/testsrc.m3u8':
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if slices.EqualComparableElements(os.Args[1:], []string{"-f", "avfoundation", "-list_devices", "true", "-i", ""}) {
|
||||
if err := slices.EqualComparableElements(os.Args[1:], []string{"-f", "avfoundation", "-list_devices", "true", "-i", ""}); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", avfoundation)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@ -10,6 +10,31 @@ import (
|
||||
"github.com/datarhei/core/v16/psutil"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
Mem MemoryInfo
|
||||
CPU CPUInfo
|
||||
}
|
||||
|
||||
type MemoryInfo struct {
|
||||
Total uint64 // bytes
|
||||
Available uint64 // bytes
|
||||
Used uint64 // bytes
|
||||
Limit uint64 // bytes
|
||||
Throttling bool
|
||||
Error error
|
||||
}
|
||||
|
||||
type CPUInfo struct {
|
||||
NCPU float64 // number of cpus
|
||||
System float64 // percent 0-100
|
||||
User float64 // percent 0-100
|
||||
Idle float64 // percent 0-100
|
||||
Other float64 // percent 0-100
|
||||
Limit float64 // percent 0-100
|
||||
Throttling bool
|
||||
Error error
|
||||
}
|
||||
|
||||
type resources struct {
|
||||
psutil psutil.Util
|
||||
|
||||
@ -34,16 +59,20 @@ type Resources interface {
|
||||
Start()
|
||||
Stop()
|
||||
|
||||
// HasLimits returns whether any limits have been set
|
||||
// HasLimits returns whether any limits have been set.
|
||||
HasLimits() bool
|
||||
|
||||
// Limits returns the CPU (percent 0-100) and memory (bytes) limits
|
||||
// Limits returns the CPU (percent 0-100) and memory (bytes) limits.
|
||||
Limits() (float64, uint64)
|
||||
|
||||
// ShouldLimit returns whether cpu and/or memory is currently limited
|
||||
// ShouldLimit returns whether cpu and/or memory is currently limited.
|
||||
ShouldLimit() (bool, bool)
|
||||
|
||||
// Request checks whether the requested resources are available.
|
||||
Request(cpu float64, memory uint64) error
|
||||
|
||||
// Info returns the current resource usage
|
||||
Info() Info
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@ -290,3 +319,38 @@ func (r *resources) Request(cpu float64, memory uint64) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resources) Info() Info {
|
||||
cpulimit, memlimit := r.Limits()
|
||||
cputhrottling, memthrottling := r.ShouldLimit()
|
||||
|
||||
cpustat, cpuerr := r.psutil.CPUPercent()
|
||||
memstat, memerr := r.psutil.VirtualMemory()
|
||||
|
||||
cpuinfo := CPUInfo{
|
||||
NCPU: r.ncpu,
|
||||
System: cpustat.System,
|
||||
User: cpustat.User,
|
||||
Idle: cpustat.Idle,
|
||||
Other: cpustat.Other,
|
||||
Limit: cpulimit,
|
||||
Throttling: cputhrottling,
|
||||
Error: cpuerr,
|
||||
}
|
||||
|
||||
meminfo := MemoryInfo{
|
||||
Total: memstat.Total,
|
||||
Available: memstat.Available,
|
||||
Used: memstat.Used,
|
||||
Limit: memlimit,
|
||||
Throttling: memthrottling,
|
||||
Error: memerr,
|
||||
}
|
||||
|
||||
i := Info{
|
||||
CPU: cpuinfo,
|
||||
Mem: meminfo,
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
3
restream/app/metadata.go
Normal file
3
restream/app/metadata.go
Normal file
@ -0,0 +1,3 @@
|
||||
package app
|
||||
|
||||
type Metadata interface{}
|
||||
@ -3,6 +3,7 @@ package app
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -143,6 +144,15 @@ func (config *Config) CreateCommand() []string {
|
||||
return command
|
||||
}
|
||||
|
||||
func (config *Config) String() string {
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (config *Config) Hash() []byte {
|
||||
b := bytes.Buffer{}
|
||||
|
||||
@ -316,3 +326,12 @@ func (p *ProcessID) Parse(pid string) {
|
||||
p.ID = pid[:i]
|
||||
p.Domain = pid[i+1:]
|
||||
}
|
||||
|
||||
func (p ProcessID) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
|
||||
func (p *ProcessID) UnmarshalText(text []byte) error {
|
||||
p.Parse(string(text))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -9,15 +9,15 @@ type LogLine struct {
|
||||
Data string
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
type ReportEntry struct {
|
||||
CreatedAt time.Time
|
||||
Prelude []string
|
||||
Log []LogLine
|
||||
Matches []string
|
||||
}
|
||||
|
||||
type LogHistoryEntry struct {
|
||||
LogEntry
|
||||
type ReportHistoryEntry struct {
|
||||
ReportEntry
|
||||
|
||||
ExitedAt time.Time
|
||||
ExitState string
|
||||
@ -25,12 +25,12 @@ type LogHistoryEntry struct {
|
||||
Usage ProcessUsage
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
LogEntry
|
||||
History []LogHistoryEntry
|
||||
type Report struct {
|
||||
ReportEntry
|
||||
History []ReportHistoryEntry
|
||||
}
|
||||
|
||||
type LogHistorySearchResult struct {
|
||||
type ReportHistorySearchResult struct {
|
||||
ProcessID string
|
||||
Reference string
|
||||
ExitState string
|
||||
@ -45,21 +45,21 @@ type Restreamer interface {
|
||||
SetMetadata(key string, data interface{}) error // Set general metadata
|
||||
GetMetadata(key string) (interface{}, error) // Get previously set general metadata
|
||||
|
||||
AddProcess(config *app.Config) error // Add a new process
|
||||
GetProcessIDs(idpattern, refpattern, ownerpattern, domainpattern string) []app.ProcessID // Get a list of process IDs based on patterns for ID and reference
|
||||
DeleteProcess(id app.ProcessID) error // Delete a process
|
||||
UpdateProcess(id app.ProcessID, config *app.Config) error // Update a process
|
||||
StartProcess(id app.ProcessID) error // Start a process
|
||||
StopProcess(id app.ProcessID) error // Stop a process
|
||||
RestartProcess(id app.ProcessID) error // Restart a process
|
||||
ReloadProcess(id app.ProcessID) error // Reload a process
|
||||
GetProcess(id app.ProcessID) (*app.Process, error) // Get a process
|
||||
GetProcessState(id app.ProcessID) (*app.State, error) // Get the state of a process
|
||||
GetProcessLog(id app.ProcessID) (*app.Log, error) // Get the logs of a process
|
||||
SearchProcessLogHistory(idpattern, refpattern, state string, from, to *time.Time) []app.LogHistorySearchResult // Search the log history of all processes
|
||||
GetPlayout(id app.ProcessID, inputid string) (string, error) // Get the URL of the playout API for a process
|
||||
SetProcessMetadata(id app.ProcessID, key string, data interface{}) error // Set metatdata to a process
|
||||
GetProcessMetadata(id app.ProcessID, key string) (interface{}, error) // Get previously set metadata from a process
|
||||
AddProcess(config *app.Config) error // Add a new process
|
||||
GetProcessIDs(idpattern, refpattern, ownerpattern, domainpattern string) []app.ProcessID // Get a list of process IDs based on patterns for ID and reference
|
||||
DeleteProcess(id app.ProcessID) error // Delete a process
|
||||
UpdateProcess(id app.ProcessID, config *app.Config) error // Update a process
|
||||
StartProcess(id app.ProcessID) error // Start a process
|
||||
StopProcess(id app.ProcessID) error // Stop a process
|
||||
RestartProcess(id app.ProcessID) error // Restart a process
|
||||
ReloadProcess(id app.ProcessID) error // Reload a process
|
||||
GetProcess(id app.ProcessID) (*app.Process, error) // Get a process
|
||||
GetProcessState(id app.ProcessID) (*app.State, error) // Get the state of a process
|
||||
GetProcessLog(id app.ProcessID) (*app.Report, error) // Get the logs of a process
|
||||
SearchProcessLogHistory(idpattern, refpattern, state string, from, to *time.Time) []app.ReportHistorySearchResult // Search the log history of all processes
|
||||
GetPlayout(id app.ProcessID, inputid string) (string, error) // Get the URL of the playout API for a process
|
||||
SetProcessMetadata(id app.ProcessID, key string, data interface{}) error // Set metatdata to a process
|
||||
GetProcessMetadata(id app.ProcessID, key string) (interface{}, error) // Get previously set metadata from a process
|
||||
|
||||
Probe(config *app.Config, timeout time.Duration) app.Probe // Probe a process with specific timeout
|
||||
}
|
||||
@ -1777,8 +1777,8 @@ func convertProgressFromParser(progress *app.Progress, pprogress parse.Progress)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *restream) GetProcessLog(id app.ProcessID) (*app.Log, error) {
|
||||
log := &app.Log{}
|
||||
func (r *restream) GetProcessLog(id app.ProcessID) (*app.Report, error) {
|
||||
log := &app.Report{}
|
||||
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
@ -1808,8 +1808,8 @@ func (r *restream) GetProcessLog(id app.ProcessID) (*app.Log, error) {
|
||||
history := task.parser.ReportHistory()
|
||||
|
||||
for _, h := range history {
|
||||
e := app.LogHistoryEntry{
|
||||
LogEntry: app.LogEntry{
|
||||
e := app.ReportHistoryEntry{
|
||||
ReportEntry: app.ReportEntry{
|
||||
CreatedAt: h.CreatedAt,
|
||||
Prelude: h.Prelude,
|
||||
Matches: h.Matches,
|
||||
@ -1849,9 +1849,9 @@ func (r *restream) GetProcessLog(id app.ProcessID) (*app.Log, error) {
|
||||
e.Progress.Output[i].ID = task.process.Config.Output[p.Index].ID
|
||||
}
|
||||
|
||||
e.LogEntry.Log = make([]app.LogLine, len(h.Log))
|
||||
e.ReportEntry.Log = make([]app.LogLine, len(h.Log))
|
||||
for i, line := range h.Log {
|
||||
e.LogEntry.Log[i] = app.LogLine{
|
||||
e.ReportEntry.Log[i] = app.LogLine{
|
||||
Timestamp: line.Timestamp,
|
||||
Data: line.Data,
|
||||
}
|
||||
@ -1863,8 +1863,8 @@ func (r *restream) GetProcessLog(id app.ProcessID) (*app.Log, error) {
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func (r *restream) SearchProcessLogHistory(idpattern, refpattern, state string, from, to *time.Time) []app.LogHistorySearchResult {
|
||||
result := []app.LogHistorySearchResult{}
|
||||
func (r *restream) SearchProcessLogHistory(idpattern, refpattern, state string, from, to *time.Time) []app.ReportHistorySearchResult {
|
||||
result := []app.ReportHistorySearchResult{}
|
||||
|
||||
ids := r.GetProcessIDs(idpattern, refpattern, "", "")
|
||||
|
||||
@ -1880,7 +1880,7 @@ func (r *restream) SearchProcessLogHistory(idpattern, refpattern, state string,
|
||||
presult := task.parser.SearchReportHistory(state, from, to)
|
||||
|
||||
for _, f := range presult {
|
||||
result = append(result, app.LogHistorySearchResult{
|
||||
result = append(result, app.ReportHistorySearchResult{
|
||||
ProcessID: task.id,
|
||||
Reference: task.reference,
|
||||
ExitState: f.ExitState,
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
iamidentity "github.com/datarhei/core/v16/iam/identity"
|
||||
"github.com/datarhei/core/v16/rtmp"
|
||||
rtmpurl "github.com/datarhei/core/v16/rtmp/url"
|
||||
srturl "github.com/datarhei/core/v16/srt/url"
|
||||
)
|
||||
|
||||
@ -124,7 +124,7 @@ func (g *rewrite) rtmpURL(u *url.URL, _ Access, identity iamidentity.Verifier) s
|
||||
token := identity.GetServiceToken()
|
||||
|
||||
// Remove the existing token from the path
|
||||
path, _, _ := rtmp.GetToken(u)
|
||||
path, _, _ := rtmpurl.GetToken(u)
|
||||
u.Path = path
|
||||
|
||||
q := u.Query()
|
||||
|
||||
74
rtmp/rtmp.go
74
rtmp/rtmp.go
@ -5,17 +5,17 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster/proxy"
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
enctoken "github.com/datarhei/core/v16/encoding/token"
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
iamidentity "github.com/datarhei/core/v16/iam/identity"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
rtmpurl "github.com/datarhei/core/v16/rtmp/url"
|
||||
"github.com/datarhei/core/v16/session"
|
||||
|
||||
"github.com/datarhei/joy4/av/avutil"
|
||||
@ -61,7 +61,7 @@ type Config struct {
|
||||
// with methods like tls.Config.SetSessionTicketKeys.
|
||||
TLSConfig *tls.Config
|
||||
|
||||
Proxy proxy.ProxyReader
|
||||
Proxy *node.Manager
|
||||
|
||||
IAM iam.IAM
|
||||
}
|
||||
@ -98,7 +98,7 @@ type server struct {
|
||||
channels map[string]*channel
|
||||
lock sync.RWMutex
|
||||
|
||||
proxy proxy.ProxyReader
|
||||
proxy *node.Manager
|
||||
|
||||
iam iam.IAM
|
||||
}
|
||||
@ -203,68 +203,14 @@ func (s *server) log(who, handler, action, resource, message string, client net.
|
||||
}).Log(message)
|
||||
}
|
||||
|
||||
// GetToken returns the path without the token and the token found in the URL and whether
|
||||
// it was found in the path. If the token was part of the path, the token is removed from
|
||||
// the path. The token in the query string takes precedence. The token in the path is
|
||||
// assumed to be the last path element.
|
||||
func GetToken(u *url.URL) (string, string, bool) {
|
||||
q := u.Query()
|
||||
if q.Has("token") {
|
||||
// The token was in the query. Return the unmomdified path and the token.
|
||||
return u.Path, q.Get("token"), false
|
||||
}
|
||||
|
||||
pathElements := splitPath(u.EscapedPath())
|
||||
nPathElements := len(pathElements)
|
||||
|
||||
if nPathElements <= 1 {
|
||||
return u.Path, "", false
|
||||
}
|
||||
|
||||
rawPath := "/" + strings.Join(pathElements[:nPathElements-1], "/")
|
||||
rawToken := pathElements[nPathElements-1]
|
||||
|
||||
path, err := url.PathUnescape(rawPath)
|
||||
if err != nil {
|
||||
path = rawPath
|
||||
}
|
||||
|
||||
token, err := url.PathUnescape(rawToken)
|
||||
if err != nil {
|
||||
token = rawToken
|
||||
}
|
||||
|
||||
// Return the path without the token
|
||||
return path, token, true
|
||||
}
|
||||
|
||||
func splitPath(path string) []string {
|
||||
pathElements := strings.Split(filepath.Clean(path), "/")
|
||||
|
||||
if len(pathElements) == 0 {
|
||||
return pathElements
|
||||
}
|
||||
|
||||
if len(pathElements[0]) == 0 {
|
||||
pathElements = pathElements[1:]
|
||||
}
|
||||
|
||||
return pathElements
|
||||
}
|
||||
|
||||
func removePathPrefix(path, prefix string) (string, string) {
|
||||
prefix = filepath.Join("/", prefix)
|
||||
return filepath.Join("/", strings.TrimPrefix(path, prefix+"/")), prefix
|
||||
}
|
||||
|
||||
// handlePlay is called when a RTMP client wants to play a stream
|
||||
func (s *server) handlePlay(conn *rtmp.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
remote := conn.NetConn().RemoteAddr()
|
||||
playpath, token, isStreamkey := GetToken(conn.URL)
|
||||
playpath, token, isStreamkey := rtmpurl.GetToken(conn.URL)
|
||||
|
||||
playpath, _ = removePathPrefix(playpath, s.app)
|
||||
playpath, _ = rtmpurl.RemovePathPrefix(playpath, s.app)
|
||||
|
||||
identity, err := s.findIdentityFromStreamKey(token)
|
||||
if err != nil {
|
||||
@ -293,7 +239,7 @@ func (s *server) handlePlay(conn *rtmp.Conn) {
|
||||
|
||||
if ch == nil && s.proxy != nil {
|
||||
// Check in the cluster for that stream
|
||||
url, err := s.proxy.GetURL("rtmp", playpath)
|
||||
url, err := s.proxy.MediaGetURL("rtmp", playpath)
|
||||
if err != nil {
|
||||
s.log(identity, "PLAY", "NOTFOUND", playpath, "", remote)
|
||||
return
|
||||
@ -390,9 +336,9 @@ func (s *server) handlePublish(conn *rtmp.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
remote := conn.NetConn().RemoteAddr()
|
||||
playpath, token, isStreamkey := GetToken(conn.URL)
|
||||
playpath, token, isStreamkey := rtmpurl.GetToken(conn.URL)
|
||||
|
||||
playpath, app := removePathPrefix(playpath, s.app)
|
||||
playpath, app := rtmpurl.RemovePathPrefix(playpath, s.app)
|
||||
|
||||
identity, err := s.findIdentityFromStreamKey(token)
|
||||
if err != nil {
|
||||
@ -534,7 +480,7 @@ func (s *server) findIdentityFromStreamKey(key string) (string, error) {
|
||||
// considered the domain. It is assumed that the app is not part of
|
||||
// the provided path.
|
||||
func (s *server) findDomainFromPlaypath(path string) string {
|
||||
elements := splitPath(path)
|
||||
elements := rtmpurl.SplitPath(path)
|
||||
if len(elements) == 1 {
|
||||
return "$none"
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
rtmpurl "github.com/datarhei/core/v16/rtmp/url"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -20,7 +22,7 @@ func TestToken(t *testing.T) {
|
||||
u, err := url.Parse(d[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
path, token, _ := GetToken(u)
|
||||
path, token, _ := rtmpurl.GetToken(u)
|
||||
|
||||
require.Equal(t, d[1], path, "url=%s", u.String())
|
||||
require.Equal(t, d[2], token, "url=%s", u.String())
|
||||
@ -35,7 +37,7 @@ func TestSplitPath(t *testing.T) {
|
||||
}
|
||||
|
||||
for path, split := range data {
|
||||
elms := splitPath(path)
|
||||
elms := rtmpurl.SplitPath(path)
|
||||
|
||||
require.ElementsMatch(t, split, elms, "%s", path)
|
||||
}
|
||||
@ -49,7 +51,7 @@ func TestRemovePathPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
x, _ := removePathPrefix(d[0], d[1])
|
||||
x, _ := rtmpurl.RemovePathPrefix(d[0], d[1])
|
||||
|
||||
require.Equal(t, d[2], x, "path=%s prefix=%s", d[0], d[1])
|
||||
}
|
||||
|
||||
61
rtmp/url/url.go
Normal file
61
rtmp/url/url.go
Normal file
@ -0,0 +1,61 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetToken returns the path without the token and the token found in the URL and whether
|
||||
// it was found in the path. If the token was part of the path, the token is removed from
|
||||
// the path. The token in the query string takes precedence. The token in the path is
|
||||
// assumed to be the last path element.
|
||||
func GetToken(u *url.URL) (string, string, bool) {
|
||||
q := u.Query()
|
||||
if q.Has("token") {
|
||||
// The token was in the query. Return the unmomdified path and the token.
|
||||
return u.Path, q.Get("token"), false
|
||||
}
|
||||
|
||||
pathElements := SplitPath(u.EscapedPath())
|
||||
nPathElements := len(pathElements)
|
||||
|
||||
if nPathElements <= 1 {
|
||||
return u.Path, "", false
|
||||
}
|
||||
|
||||
rawPath := "/" + strings.Join(pathElements[:nPathElements-1], "/")
|
||||
rawToken := pathElements[nPathElements-1]
|
||||
|
||||
path, err := url.PathUnescape(rawPath)
|
||||
if err != nil {
|
||||
path = rawPath
|
||||
}
|
||||
|
||||
token, err := url.PathUnescape(rawToken)
|
||||
if err != nil {
|
||||
token = rawToken
|
||||
}
|
||||
|
||||
// Return the path without the token
|
||||
return path, token, true
|
||||
}
|
||||
|
||||
func SplitPath(path string) []string {
|
||||
pathElements := strings.Split(filepath.Clean(path), "/")
|
||||
|
||||
if len(pathElements) == 0 {
|
||||
return pathElements
|
||||
}
|
||||
|
||||
if len(pathElements[0]) == 0 {
|
||||
pathElements = pathElements[1:]
|
||||
}
|
||||
|
||||
return pathElements
|
||||
}
|
||||
|
||||
func RemovePathPrefix(path, prefix string) (string, string) {
|
||||
prefix = filepath.Join("/", prefix)
|
||||
return filepath.Join("/", strings.TrimPrefix(path, prefix+"/")), prefix
|
||||
}
|
||||
@ -59,7 +59,7 @@ func DiffEqualer[T any, X Equaler[T]](listA []T, listB []X) ([]T, []X) {
|
||||
if visited[j] {
|
||||
continue
|
||||
}
|
||||
if listB[j].Equal(element) {
|
||||
if listB[j].Equal(element) == nil {
|
||||
visited[j] = true
|
||||
found = true
|
||||
break
|
||||
|
||||
@ -1,28 +1,54 @@
|
||||
package slices
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EqualComparableElements returns whether two slices have the same elements.
|
||||
func EqualComparableElements[T comparable](a, b []T) bool {
|
||||
func EqualComparableElements[T comparable](a, b []T) error {
|
||||
extraA, extraB := DiffComparable(a, b)
|
||||
|
||||
if len(extraA) == 0 && len(extraB) == 0 {
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
return false
|
||||
diff := []string{}
|
||||
|
||||
for _, e := range extraA {
|
||||
diff = append(diff, fmt.Sprintf("+ %v", e))
|
||||
}
|
||||
|
||||
for _, e := range extraB {
|
||||
diff = append(diff, fmt.Sprintf("- %v", e))
|
||||
}
|
||||
|
||||
return errors.New(strings.Join(diff, ","))
|
||||
}
|
||||
|
||||
// Equaler defines a type that implements the Equal function.
|
||||
type Equaler[T any] interface {
|
||||
Equal(T) bool
|
||||
Equal(T) error
|
||||
}
|
||||
|
||||
// EqualEqualerElements returns whether two slices of Equaler have the same elements.
|
||||
func EqualEqualerElements[T any, X Equaler[T]](a []T, b []X) bool {
|
||||
func EqualEqualerElements[T any, X Equaler[T]](a []T, b []X) error {
|
||||
extraA, extraB := DiffEqualer(a, b)
|
||||
|
||||
if len(extraA) == 0 && len(extraB) == 0 {
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
return false
|
||||
diff := []string{}
|
||||
|
||||
for _, e := range extraA {
|
||||
diff = append(diff, fmt.Sprintf("- %v", e))
|
||||
}
|
||||
|
||||
for _, e := range extraB {
|
||||
diff = append(diff, fmt.Sprintf("+ %v", e))
|
||||
}
|
||||
|
||||
return errors.New(strings.Join(diff, ","))
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package slices
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -10,42 +11,46 @@ func TestEqualComparableElements(t *testing.T) {
|
||||
a := []string{"a", "b", "c", "d"}
|
||||
b := []string{"b", "c", "a", "d"}
|
||||
|
||||
ok := EqualComparableElements(a, b)
|
||||
require.True(t, ok)
|
||||
err := EqualComparableElements(a, b)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok = EqualComparableElements(b, a)
|
||||
require.True(t, ok)
|
||||
err = EqualComparableElements(b, a)
|
||||
require.NoError(t, err)
|
||||
|
||||
a = append(a, "z")
|
||||
|
||||
ok = EqualComparableElements(a, b)
|
||||
require.False(t, ok)
|
||||
err = EqualComparableElements(a, b)
|
||||
require.Error(t, err)
|
||||
|
||||
ok = EqualComparableElements(b, a)
|
||||
require.False(t, ok)
|
||||
err = EqualComparableElements(b, a)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
type String string
|
||||
|
||||
func (a String) Equal(b String) bool {
|
||||
return string(a) == string(b)
|
||||
func (a String) Equal(b String) error {
|
||||
if string(a) == string(b) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s != %s", a, b)
|
||||
}
|
||||
|
||||
func TestEqualEqualerElements(t *testing.T) {
|
||||
a := []String{"a", "b", "c", "d"}
|
||||
b := []String{"b", "c", "a", "d"}
|
||||
|
||||
ok := EqualEqualerElements(a, b)
|
||||
require.True(t, ok)
|
||||
err := EqualEqualerElements(a, b)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok = EqualEqualerElements(b, a)
|
||||
require.True(t, ok)
|
||||
err = EqualEqualerElements(b, a)
|
||||
require.NoError(t, err)
|
||||
|
||||
a = append(a, "z")
|
||||
|
||||
ok = EqualEqualerElements(a, b)
|
||||
require.False(t, ok)
|
||||
err = EqualEqualerElements(a, b)
|
||||
require.Error(t, err)
|
||||
|
||||
ok = EqualEqualerElements(b, a)
|
||||
require.False(t, ok)
|
||||
err = EqualEqualerElements(b, a)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/cluster/proxy"
|
||||
"github.com/datarhei/core/v16/cluster/node"
|
||||
enctoken "github.com/datarhei/core/v16/encoding/token"
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
iamidentity "github.com/datarhei/core/v16/iam/identity"
|
||||
@ -45,7 +45,7 @@ type Config struct {
|
||||
|
||||
SRTLogTopics []string
|
||||
|
||||
Proxy proxy.ProxyReader
|
||||
Proxy *node.Manager
|
||||
|
||||
IAM iam.IAM
|
||||
}
|
||||
@ -84,7 +84,7 @@ type server struct {
|
||||
srtlog map[string]*ring.Ring // Per logtopic a dedicated ring buffer
|
||||
srtlogLock sync.RWMutex
|
||||
|
||||
proxy proxy.ProxyReader
|
||||
proxy *node.Manager
|
||||
|
||||
iam iam.IAM
|
||||
}
|
||||
@ -423,7 +423,7 @@ func (s *server) handleSubscribe(conn srt.Conn) {
|
||||
}
|
||||
|
||||
// Check in the cluster for the stream and proxy it
|
||||
srturl, err := s.proxy.GetURL("srt", si.Resource)
|
||||
srturl, err := s.proxy.MediaGetURL("srt", si.Resource)
|
||||
if err != nil {
|
||||
s.log(identity, "PLAY", "NOTFOUND", si.Resource, "no publisher for this resource found", client)
|
||||
return
|
||||
|
||||
@ -2,7 +2,6 @@ package url
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
neturl "net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -111,7 +110,7 @@ func (si *StreamInfo) String() string {
|
||||
func ParseStreamId(streamid string) (StreamInfo, error) {
|
||||
si := StreamInfo{Mode: "request"}
|
||||
|
||||
if decodedStreamid, err := url.QueryUnescape(streamid); err == nil {
|
||||
if decodedStreamid, err := neturl.QueryUnescape(streamid); err == nil {
|
||||
streamid = decodedStreamid
|
||||
}
|
||||
|
||||
|
||||
21
vendor/github.com/datarhei/core-client-go/v16/LICENSE
generated
vendored
21
vendor/github.com/datarhei/core-client-go/v16/LICENSE
generated
vendored
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 FOSS GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
312
vendor/github.com/datarhei/core-client-go/v16/README.md
generated
vendored
312
vendor/github.com/datarhei/core-client-go/v16/README.md
generated
vendored
@ -1,312 +0,0 @@
|
||||
# core-client-go
|
||||
|
||||
A golang client for the `github.com/datarhei/core` API.
|
||||
|
||||
---
|
||||
|
||||
- [Quick Start](#quick-start)
|
||||
- [API definitions](#api-definitions)
|
||||
- [General](#general)
|
||||
- [Config](#config)
|
||||
- [Disk filesystem](#disk-filesystem)
|
||||
- [In-memory filesystem](#in-memory-filesystem)
|
||||
- [Log](#log)
|
||||
- [Metadata](#metadata)
|
||||
- [Metrics](#metrics)
|
||||
- [Process](#process)
|
||||
- [RTMP](#rtmp)
|
||||
- [Session](#session)
|
||||
- [Skills](#skills)
|
||||
- [Versioning](#versioning)
|
||||
- [Contributing](#contributing)
|
||||
- [Licence](#licence)
|
||||
|
||||
## Quick Start
|
||||
|
||||
Example for retrieving a list of all processes:
|
||||
|
||||
```
|
||||
import "github.com/datarhei/core-client-go/v16"
|
||||
|
||||
client, err := coreclient.New(coreclient.Config{
|
||||
Address: "https://example.com:8080",
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
})
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
|
||||
processes, err := client.ProcessList(coreclient.ProcessListOptions{})
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## API definitions
|
||||
|
||||
### General
|
||||
|
||||
- `GET` /api
|
||||
|
||||
```golang
|
||||
About() api.About
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
- `GET` /api/v3/config
|
||||
|
||||
```golang
|
||||
Config() (api.Config, error)
|
||||
```
|
||||
|
||||
- `PUT` /api/v3/config
|
||||
|
||||
```golang
|
||||
ConfigSet(config api.ConfigSet) error
|
||||
```
|
||||
|
||||
- `GET` /api/v3/config/reload
|
||||
|
||||
```golang
|
||||
ConfigReload() error
|
||||
```
|
||||
|
||||
### Disk filesystem
|
||||
|
||||
- `GET` /api/v3/fs/disk
|
||||
|
||||
```golang
|
||||
DiskFSList(sort, order string) ([]api.FileInfo, error)
|
||||
```
|
||||
|
||||
- `HEAD` /api/v3/fs/disk/{path}
|
||||
|
||||
```golang
|
||||
DiskFSHasFile(path string) bool
|
||||
```
|
||||
|
||||
- `GET` /api/v3/fs/disk/{path}
|
||||
|
||||
```golang
|
||||
DiskFSGetFile(path string) (io.ReadCloser, error)
|
||||
```
|
||||
|
||||
- `DELETE` /api/v3/fs/disk/{path}
|
||||
|
||||
```golang
|
||||
DiskFSDeleteFile(path string) error
|
||||
```
|
||||
|
||||
- `PUT` /api/v3/fs/disk/{path}
|
||||
```golang
|
||||
DiskFSAddFile(path string, data io.Reader) error
|
||||
```
|
||||
|
||||
### In-memory filesystem
|
||||
|
||||
- `GET` /api/v3/fs/mem
|
||||
|
||||
```golang
|
||||
MemFSList(sort, order string) ([]api.FileInfo, error)
|
||||
```
|
||||
|
||||
- `HEAD` /api/v3/fs/mem/{path}
|
||||
|
||||
```golang
|
||||
MemFSHasFile(path string) bool
|
||||
```
|
||||
|
||||
- `GET` /api/v3/fs/mem/{path}
|
||||
|
||||
```golang
|
||||
MemFSGetFile(path string) (io.ReadCloser, error)
|
||||
```
|
||||
|
||||
- `DELETE` /api/v3/fs/mem/{path}
|
||||
|
||||
```golang
|
||||
MemFSDeleteFile(path string) error
|
||||
```
|
||||
|
||||
- `PUT` /api/v3/fs/mem/{path}
|
||||
```golang
|
||||
MemFSAddFile(path string, data io.Reader) error
|
||||
```
|
||||
|
||||
### Log
|
||||
|
||||
- `GET` /api/v3/log
|
||||
|
||||
```golang
|
||||
Log() ([]api.LogEvent, error)
|
||||
```
|
||||
|
||||
### Metadata
|
||||
|
||||
- `GET` /api/v3/metadata/{key}
|
||||
|
||||
```golang
|
||||
Metadata(id, key string) (api.Metadata, error)
|
||||
```
|
||||
|
||||
- `PUT` /api/v3/metadata/{key}
|
||||
```golang
|
||||
MetadataSet(id, key string, metadata api.Metadata) error
|
||||
```
|
||||
|
||||
### Metrics
|
||||
|
||||
- `GET` /api/v3/metrics
|
||||
|
||||
```golang
|
||||
MetricsList() ([]api.MetricsDescription, error)
|
||||
```
|
||||
|
||||
- `POST` /api/v3/metrics
|
||||
|
||||
```golang
|
||||
Metrics(query api.MetricsQuery) (api.MetricsResponse, error)
|
||||
```
|
||||
|
||||
### Process
|
||||
|
||||
- `GET` /api/v3/process
|
||||
|
||||
```golang
|
||||
ProcessList(opts ProcessListOptions) ([]api.Process, error)
|
||||
```
|
||||
|
||||
- `POST` /api/v3/process
|
||||
|
||||
```golang
|
||||
ProcessAdd(p api.ProcessConfig) error
|
||||
```
|
||||
|
||||
- `GET` /api/v3/process/{id}
|
||||
|
||||
```golang
|
||||
Process(id string, filter []string) (api.Process, error)
|
||||
```
|
||||
|
||||
- `PUT` /api/v3/process/{id}
|
||||
|
||||
```golang
|
||||
ProcessUpdate(id string, p api.ProcessConfig) error
|
||||
```
|
||||
|
||||
- `DELETE` /api/v3/process/{id}
|
||||
|
||||
```golang
|
||||
ProcessDelete(id string) error
|
||||
```
|
||||
|
||||
- `PUT` /api/v3/process/{id}/command
|
||||
|
||||
```golang
|
||||
ProcessCommand(id, command string) error
|
||||
```
|
||||
|
||||
- `GET` /api/v3/process/{id}/probe
|
||||
|
||||
```golang
|
||||
ProcessProbe(id string) (api.Probe, error)
|
||||
```
|
||||
|
||||
- `GET` /api/v3/process/{id}/config
|
||||
|
||||
```golang
|
||||
ProcessConfig(id string) (api.ProcessConfig, error)
|
||||
```
|
||||
|
||||
- `GET` /api/v3/process/{id}/report
|
||||
|
||||
```golang
|
||||
ProcessReport(id string) (api.ProcessReport, error)
|
||||
```
|
||||
|
||||
- `GET` /api/v3/process/{id}/state
|
||||
|
||||
```golang
|
||||
ProcessState(id string) (api.ProcessState, error)
|
||||
```
|
||||
|
||||
- `GET` /api/v3/process/{id}/metadata/{key}
|
||||
|
||||
```golang
|
||||
ProcessMetadata(id, key string) (api.Metadata, error)
|
||||
```
|
||||
|
||||
- `PUT` /api/v3/process/{id}/metadata/{key}
|
||||
```golang
|
||||
ProcessMetadataSet(id, key string, metadata api.Metadata) error
|
||||
```
|
||||
|
||||
### RTMP
|
||||
|
||||
- `GET` /api/v3/rtmp
|
||||
|
||||
```golang
|
||||
RTMPChannels() ([]api.RTMPChannel, error)
|
||||
```
|
||||
|
||||
### SRT
|
||||
|
||||
- `GET` /api/v3/srt
|
||||
|
||||
```golang
|
||||
SRTChannels() (api.SRTChannels, error)
|
||||
```
|
||||
|
||||
### Session
|
||||
|
||||
- `GET` /api/v3/session
|
||||
|
||||
```golang
|
||||
Sessions(collectors []string) (api.SessionsSummary, error)
|
||||
```
|
||||
|
||||
- `GET` /api/v3/session/active
|
||||
```golang
|
||||
SessionsActive(collectors []string) (api.SessionsActive, error)
|
||||
```
|
||||
|
||||
### Skills
|
||||
|
||||
- `GET` /api/v3/skills
|
||||
|
||||
```golang
|
||||
Skills() (api.Skills, error)
|
||||
```
|
||||
|
||||
- `GET` /api/v3/skills/reload
|
||||
```golang
|
||||
SkillsReload() error
|
||||
```
|
||||
|
||||
### Widget
|
||||
|
||||
- `GET` /api/v3/widget
|
||||
|
||||
```golang
|
||||
WidgetProcess(id string) (api.WidgetProcess, error)
|
||||
```
|
||||
|
||||
## Versioning
|
||||
|
||||
The version of this module is according to which version of the datarhei Core API
|
||||
you want to connect to. Check the branches to find out which other versions are
|
||||
implemented. If you want to connect to an API version 12, you have to import the client
|
||||
module of the version 12, i.e. `import "github.com/datarhei/core-client-go/v12"`.
|
||||
|
||||
The latest implementation is on the `main` branch.
|
||||
|
||||
## Contributing
|
||||
|
||||
Found a mistake or misconduct? Create a [issue](https://github.com/datarhei/core-client-go/issues) or send a pull-request.
|
||||
Suggestions for improvement are welcome.
|
||||
|
||||
## Licence
|
||||
|
||||
[MIT](https://github.com/datarhei/core-client-go/blob/main/LICENSE)
|
||||
33
vendor/github.com/datarhei/core-client-go/v16/api/about.go
generated
vendored
33
vendor/github.com/datarhei/core-client-go/v16/api/about.go
generated
vendored
@ -1,33 +0,0 @@
|
||||
package api
|
||||
|
||||
// About is some general information about the API
|
||||
type About struct {
|
||||
App string `json:"app"`
|
||||
Auths []string `json:"auths"`
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Uptime uint64 `json:"uptime_seconds"`
|
||||
Version Version `json:"version"`
|
||||
}
|
||||
|
||||
// Version is some information about the binary
|
||||
type Version struct {
|
||||
Number string `json:"number"`
|
||||
Commit string `json:"repository_commit"`
|
||||
Branch string `json:"repository_branch"`
|
||||
Build string `json:"build_date"`
|
||||
Arch string `json:"arch"`
|
||||
Compiler string `json:"compiler"`
|
||||
}
|
||||
|
||||
// MinimalAbout is the minimal information about the API
|
||||
type MinimalAbout struct {
|
||||
App string `json:"app"`
|
||||
Auths []string `json:"auths"`
|
||||
Version VersionMinimal `json:"version"`
|
||||
}
|
||||
|
||||
type VersionMinimal struct {
|
||||
Number string `json:"number"`
|
||||
}
|
||||
23
vendor/github.com/datarhei/core-client-go/v16/api/avstream.go
generated
vendored
23
vendor/github.com/datarhei/core-client-go/v16/api/avstream.go
generated
vendored
@ -1,23 +0,0 @@
|
||||
package api
|
||||
|
||||
type AVstreamIO struct {
|
||||
State string `json:"state" enums:"running,idle" jsonschema:"enum=running,enum=idle"`
|
||||
Packet uint64 `json:"packet" format:"uint64"`
|
||||
Time uint64 `json:"time"`
|
||||
Size uint64 `json:"size_kb"`
|
||||
}
|
||||
|
||||
type AVstream struct {
|
||||
Input AVstreamIO `json:"input"`
|
||||
Output AVstreamIO `json:"output"`
|
||||
Aqueue uint64 `json:"aqueue" format:"uint64"`
|
||||
Queue uint64 `json:"queue" format:"uint64"`
|
||||
Dup uint64 `json:"dup" format:"uint64"`
|
||||
Drop uint64 `json:"drop" format:"uint64"`
|
||||
Enc uint64 `json:"enc" format:"uint64"`
|
||||
Looping bool `json:"looping"`
|
||||
LoopingRuntime uint64 `json:"looping_runtime" format:"uint64"`
|
||||
Duplicating bool `json:"duplicating"`
|
||||
GOP string `json:"gop"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user