mirror of
https://github.com/datarhei/core.git
synced 2025-10-05 16:07:07 +08:00
WIP: internal API
This commit is contained in:
@@ -623,17 +623,20 @@ func (a *api) start() error {
|
|||||||
a.restream = restream
|
a.restream = restream
|
||||||
|
|
||||||
if cfg.Cluster.Enable {
|
if cfg.Cluster.Enable {
|
||||||
if cluster, err := cluster.New(cluster.ClusterConfig{
|
cluster, err := cluster.New(cluster.ClusterConfig{
|
||||||
ID: cfg.ID,
|
ID: cfg.ID,
|
||||||
Name: cfg.Name,
|
Name: cfg.Name,
|
||||||
Path: filepath.Join(cfg.DB.Dir, "cluster"),
|
Path: filepath.Join(cfg.DB.Dir, "cluster"),
|
||||||
IPLimiter: a.sessionsLimiter,
|
IPLimiter: a.sessionsLimiter,
|
||||||
Logger: a.log.logger.core.WithComponent("Cluster"),
|
Logger: a.log.logger.core.WithComponent("Cluster"),
|
||||||
}); err != nil {
|
Bootstrap: cfg.Cluster.Bootstrap,
|
||||||
|
Address: cfg.Cluster.Address,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create cluster: %w", err)
|
return fmt.Errorf("unable to create cluster: %w", err)
|
||||||
} else {
|
|
||||||
a.cluster = cluster
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.cluster = cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpjwt jwt.JWT
|
var httpjwt jwt.JWT
|
||||||
|
@@ -2,11 +2,13 @@ package cluster
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
gonet "net"
|
gonet "net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -18,6 +20,26 @@ import (
|
|||||||
"go.etcd.io/bbolt"
|
"go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
/api/v3:
|
||||||
|
GET /cluster/db/node - list all nodes that are stored in the FSM - Cluster.Store.ListNodes()
|
||||||
|
POST /cluster/db/node - add a node to the FSM - Cluster.Store.AddNode()
|
||||||
|
DELETE /cluster/db/node/:id - remove a node from the FSM - Cluster.Store.RemoveNode()
|
||||||
|
|
||||||
|
GET /cluster/db/process - list all process configs that are stored in the FSM - Cluster.Store.ListProcesses()
|
||||||
|
POST /cluster/db/process - add a process config to the FSM - Cluster.Store.AddProcess()
|
||||||
|
PUT /cluster/db/process/:id - update a process config in the FSM - Cluster.Store.UpdateProcess()
|
||||||
|
DELETE /cluster/db/process/:id - remove a process config from the FSM - Cluster.Store.RemoveProcess()
|
||||||
|
|
||||||
|
** for the processes, the leader will decide where to actually run them. the process configs will
|
||||||
|
also be added to the regular process DB of each core.
|
||||||
|
|
||||||
|
POST /cluster/join - join the cluster - Cluster.Join()
|
||||||
|
DELETE /cluster/:id - leave the cluster - Cluster.Leave()
|
||||||
|
|
||||||
|
** all these endpoints will forward the request to the leader.
|
||||||
|
*/
|
||||||
|
|
||||||
var ErrNodeNotFound = errors.New("node not found")
|
var ErrNodeNotFound = errors.New("node not found")
|
||||||
|
|
||||||
type ClusterReader interface {
|
type ClusterReader interface {
|
||||||
@@ -55,6 +77,8 @@ type ClusterConfig struct {
|
|||||||
Path string
|
Path string
|
||||||
IPLimiter net.IPLimiter
|
IPLimiter net.IPLimiter
|
||||||
Logger log.Logger
|
Logger log.Logger
|
||||||
|
Bootstrap bool
|
||||||
|
Address string
|
||||||
}
|
}
|
||||||
|
|
||||||
type cluster struct {
|
type cluster struct {
|
||||||
@@ -84,6 +108,8 @@ type cluster struct {
|
|||||||
raftStore *raftboltdb.BoltStore
|
raftStore *raftboltdb.BoltStore
|
||||||
raftRemoveGracePeriod time.Duration
|
raftRemoveGracePeriod time.Duration
|
||||||
|
|
||||||
|
store Store
|
||||||
|
|
||||||
reassertLeaderCh chan chan error
|
reassertLeaderCh chan chan error
|
||||||
|
|
||||||
leaveCh chan struct{}
|
leaveCh chan struct{}
|
||||||
@@ -106,6 +132,8 @@ func New(config ClusterConfig) (Cluster, error) {
|
|||||||
updates: make(chan NodeState, 64),
|
updates: make(chan NodeState, 64),
|
||||||
logger: config.Logger,
|
logger: config.Logger,
|
||||||
|
|
||||||
|
raftAddress: config.Address,
|
||||||
|
|
||||||
reassertLeaderCh: make(chan chan error),
|
reassertLeaderCh: make(chan chan error),
|
||||||
leaveCh: make(chan struct{}),
|
leaveCh: make(chan struct{}),
|
||||||
shutdownCh: make(chan struct{}),
|
shutdownCh: make(chan struct{}),
|
||||||
@@ -119,12 +147,19 @@ func New(config ClusterConfig) (Cluster, error) {
|
|||||||
c.logger = log.New("")
|
c.logger = log.New("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fsm, err := NewFSM()
|
store, err := NewStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.startRaft(fsm, true, false)
|
c.store = store
|
||||||
|
|
||||||
|
c.logger.Debug().Log("starting raft")
|
||||||
|
|
||||||
|
err = c.startRaft(store, config.Bootstrap, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
@@ -213,6 +248,7 @@ func (c *cluster) Leave() error {
|
|||||||
if err := c.leadershipTransfer(); err == nil {
|
if err := c.leadershipTransfer(); err == nil {
|
||||||
isLeader = false
|
isLeader = false
|
||||||
} else {
|
} else {
|
||||||
|
c.StoreRemoveNode(c.id)
|
||||||
future := c.raft.RemoveServer(raft.ServerID(c.id), 0, 0)
|
future := c.raft.RemoveServer(raft.ServerID(c.id), 0, 0)
|
||||||
if err := future.Error(); err != nil {
|
if err := future.Error(); err != nil {
|
||||||
c.logger.Error().WithError(err).Log("failed to remove ourself as raft peer")
|
c.logger.Error().WithError(err).Log("failed to remove ourself as raft peer")
|
||||||
@@ -224,6 +260,9 @@ func (c *cluster) Leave() error {
|
|||||||
// must wait to allow the raft replication to take place, otherwise an
|
// must wait to allow the raft replication to take place, otherwise an
|
||||||
// immediate shutdown could cause a loss of quorum.
|
// immediate shutdown could cause a loss of quorum.
|
||||||
if !isLeader {
|
if !isLeader {
|
||||||
|
// Send leave-request to leader
|
||||||
|
// DELETE leader//api/v3/cluster/node/:id
|
||||||
|
|
||||||
left := false
|
left := false
|
||||||
limit := time.Now().Add(c.raftRemoveGracePeriod)
|
limit := time.Now().Add(c.raftRemoveGracePeriod)
|
||||||
for !left && time.Now().Before(limit) {
|
for !left && time.Now().Before(limit) {
|
||||||
@@ -259,6 +298,210 @@ func (c *cluster) IsLeader() bool {
|
|||||||
return c.raft.State() == raft.Leader
|
return c.raft.State() == raft.Leader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cluster) leave(id string) error {
|
||||||
|
if !c.IsLeader() {
|
||||||
|
return fmt.Errorf("not leader")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Debug().WithFields(log.Fields{
|
||||||
|
"nodeid": id,
|
||||||
|
}).Log("received leave request for remote node")
|
||||||
|
|
||||||
|
if id == c.id {
|
||||||
|
err := c.leadershipTransfer()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn().WithError(err).Log("failed to transfer leadership")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
future := c.raft.RemoveServer(raft.ServerID(id), 0, 0)
|
||||||
|
if err := future.Error(); err != nil {
|
||||||
|
c.logger.Error().WithError(err).WithFields(log.Fields{
|
||||||
|
"nodeid": id,
|
||||||
|
}).Log("failed to remove node")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) Join(id, raftAddress, apiAddress, username, password string) error {
|
||||||
|
if !c.IsLeader() {
|
||||||
|
return fmt.Errorf("not leader")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Debug().WithFields(log.Fields{
|
||||||
|
"nodeid": id,
|
||||||
|
"address": raftAddress,
|
||||||
|
}).Log("received join request for remote node")
|
||||||
|
|
||||||
|
configFuture := c.raft.GetConfiguration()
|
||||||
|
if err := configFuture.Error(); err != nil {
|
||||||
|
c.logger.Error().WithError(err).Log("failed to get raft configuration")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, srv := range configFuture.Configuration().Servers {
|
||||||
|
// If a node already exists with either the joining node's ID or address,
|
||||||
|
// that node may need to be removed from the config first.
|
||||||
|
if srv.ID == raft.ServerID(id) || srv.Address == raft.ServerAddress(raftAddress) {
|
||||||
|
// However if *both* the ID and the address are the same, then nothing -- not even
|
||||||
|
// a join operation -- is needed.
|
||||||
|
if srv.ID == raft.ServerID(id) && srv.Address == raft.ServerAddress(raftAddress) {
|
||||||
|
c.logger.Debug().WithFields(log.Fields{
|
||||||
|
"nodeid": id,
|
||||||
|
"address": raftAddress,
|
||||||
|
}).Log("node is already member of cluster, ignoring join request")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
future := c.raft.RemoveServer(srv.ID, 0, 0)
|
||||||
|
if err := future.Error(); err != nil {
|
||||||
|
c.logger.Error().WithError(err).WithFields(log.Fields{
|
||||||
|
"nodeid": id,
|
||||||
|
"address": raftAddress,
|
||||||
|
}).Log("error removing existing node")
|
||||||
|
return fmt.Errorf("error removing existing node %s at %s: %w", id, raftAddress, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f := c.raft.AddVoter(raft.ServerID(id), raft.ServerAddress(raftAddress), 0, 5*time.Second)
|
||||||
|
if err := f.Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.StoreAddNode(id, apiAddress, username, password); err != nil {
|
||||||
|
future := c.raft.RemoveServer(raft.ServerID(id), 0, 0)
|
||||||
|
if err := future.Error(); err != nil {
|
||||||
|
c.logger.Error().WithError(err).WithFields(log.Fields{
|
||||||
|
"nodeid": id,
|
||||||
|
"address": raftAddress,
|
||||||
|
}).Log("error removing existing node")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info().WithFields(log.Fields{
|
||||||
|
"nodeid": id,
|
||||||
|
"address": raftAddress,
|
||||||
|
}).Log("node joined successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type command struct {
|
||||||
|
Operation string
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type addNodeCommand struct {
|
||||||
|
ID string
|
||||||
|
Address string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) StoreListNodes() []addNodeCommand {
|
||||||
|
c.store.ListNodes()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) StoreAddNode(id, address, username, password string) error {
|
||||||
|
if !c.IsLeader() {
|
||||||
|
return fmt.Errorf("not leader")
|
||||||
|
}
|
||||||
|
|
||||||
|
com := &command{
|
||||||
|
Operation: "addNode",
|
||||||
|
Data: &addNodeCommand{
|
||||||
|
ID: id,
|
||||||
|
Address: address,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(com)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
future := c.raft.Apply(b, 5*time.Second)
|
||||||
|
if err := future.Error(); err != nil {
|
||||||
|
return fmt.Errorf("applying command failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeNodeCommand struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) StoreRemoveNode(id string) error {
|
||||||
|
if !c.IsLeader() {
|
||||||
|
return fmt.Errorf("not leader")
|
||||||
|
}
|
||||||
|
|
||||||
|
com := &command{
|
||||||
|
Operation: "removeNode",
|
||||||
|
Data: &removeNodeCommand{
|
||||||
|
ID: id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(com)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
future := c.raft.Apply(b, 5*time.Second)
|
||||||
|
if err := future.Error(); err != nil {
|
||||||
|
return fmt.Errorf("applying command failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// trackLeaderChanges registers an Observer with raft in order to receive updates
|
||||||
|
// about leader changes, in order to keep the forwarder up to date.
|
||||||
|
func (c *cluster) trackLeaderChanges() {
|
||||||
|
obsCh := make(chan raft.Observation, 16)
|
||||||
|
observer := raft.NewObserver(obsCh, false, func(o *raft.Observation) bool {
|
||||||
|
_, leaderOK := o.Data.(raft.LeaderObservation)
|
||||||
|
_, peerOK := o.Data.(raft.PeerObservation)
|
||||||
|
|
||||||
|
return leaderOK || peerOK
|
||||||
|
})
|
||||||
|
c.raft.RegisterObserver(observer)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case obs := <-obsCh:
|
||||||
|
if leaderObs, ok := obs.Data.(raft.LeaderObservation); ok {
|
||||||
|
// TODO: update the forwarder
|
||||||
|
c.logger.Debug().WithFields(log.Fields{
|
||||||
|
"id": leaderObs.LeaderID,
|
||||||
|
"address": leaderObs.LeaderAddr,
|
||||||
|
}).Log("new leader observation")
|
||||||
|
} else if peerObs, ok := obs.Data.(raft.PeerObservation); ok {
|
||||||
|
c.logger.Debug().WithFields(log.Fields{
|
||||||
|
"removed": peerObs.Removed,
|
||||||
|
"address": peerObs.Peer.Address,
|
||||||
|
}).Log("new peer observation")
|
||||||
|
} else {
|
||||||
|
c.logger.Debug().WithField("type", reflect.TypeOf(obs.Data)).Log("got unknown observation type from raft")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case <-c.shutdownCh:
|
||||||
|
c.raft.DeregisterObserver(observer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *cluster) AddNode(address, username, password string) (string, error) {
|
func (c *cluster) AddNode(address, username, password string) (string, error) {
|
||||||
node, err := newNode(address, username, password, c.updates)
|
node, err := newNode(address, username, password, c.updates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -313,6 +556,8 @@ func (c *cluster) RemoveNode(id string) error {
|
|||||||
c.limiter.RemoveBlock(ip)
|
c.limiter.RemoveBlock(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.leave(id)
|
||||||
|
|
||||||
c.logger.Info().WithFields(log.Fields{
|
c.logger.Info().WithFields(log.Fields{
|
||||||
"id": id,
|
"id": id,
|
||||||
}).Log("Removed node")
|
}).Log("Removed node")
|
||||||
@@ -435,12 +680,16 @@ func (c *cluster) startRaft(fsm raft.FSM, bootstrap, inmem bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
transport, err := raft.NewTCPTransportWithLogger(c.raftAddress, addr, 3, 10*time.Second, NewLogger(c.logger.WithComponent("raft"), hclog.Debug).Named("transport"))
|
c.logger.Debug().Log("address: %s", addr)
|
||||||
|
|
||||||
|
transport, err := raft.NewTCPTransportWithLogger(c.raftAddress, addr, 3, 10*time.Second, NewLogger(c.logger, hclog.Debug).Named("raft-transport"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshotLogger := NewLogger(c.logger.WithComponent("raft"), hclog.Debug).Named("snapshot")
|
c.raftTransport = transport
|
||||||
|
|
||||||
|
snapshotLogger := NewLogger(c.logger, hclog.Debug).Named("raft-snapshot")
|
||||||
snapshots, err := raft.NewFileSnapshotStoreWithLogger(filepath.Join(c.path, "snapshots"), 10, snapshotLogger)
|
snapshots, err := raft.NewFileSnapshotStoreWithLogger(filepath.Join(c.path, "snapshots"), 10, snapshotLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -475,7 +724,7 @@ func (c *cluster) startRaft(fsm raft.FSM, bootstrap, inmem bool) error {
|
|||||||
|
|
||||||
cfg := raft.DefaultConfig()
|
cfg := raft.DefaultConfig()
|
||||||
cfg.LocalID = raft.ServerID(c.id)
|
cfg.LocalID = raft.ServerID(c.id)
|
||||||
cfg.Logger = NewLogger(c.logger.WithComponent("raft"), hclog.Debug)
|
cfg.Logger = NewLogger(c.logger, hclog.Debug).Named("raft")
|
||||||
|
|
||||||
if bootstrap {
|
if bootstrap {
|
||||||
hasState, err := raft.HasExistingState(logStore, stableStore, snapshots)
|
hasState, err := raft.HasExistingState(logStore, stableStore, snapshots)
|
||||||
@@ -512,8 +761,11 @@ func (c *cluster) startRaft(fsm raft.FSM, bootstrap, inmem bool) error {
|
|||||||
|
|
||||||
c.raft = node
|
c.raft = node
|
||||||
|
|
||||||
|
go c.trackLeaderChanges()
|
||||||
go c.monitorLeadership()
|
go c.monitorLeadership()
|
||||||
|
|
||||||
|
c.logger.Debug().Log("raft started")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,3 +781,7 @@ func (c *cluster) shutdownRaft() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nodeLoop is run by every node in the cluster. This is mainly to check the list
|
||||||
|
// of nodes from the FSM, in order to connect to them and to fetch their file lists.
|
||||||
|
func (c *cluster) nodeLoop() {}
|
||||||
|
28
cluster/forwarder.go
Normal file
28
cluster/forwarder.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import "github.com/labstack/echo/v4"
|
||||||
|
|
||||||
|
// Forwarder forwards any HTTP request from a follower to the leader
|
||||||
|
type Forwarder interface {
|
||||||
|
SetLeader(address string)
|
||||||
|
Forward(c echo.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type forwarder struct {
|
||||||
|
leaderAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewForwarder() (Forwarder, error) {
|
||||||
|
return &forwarder{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *forwarder) SetLeader(address string) {
|
||||||
|
if f.leaderAddr == address {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *forwarder) Forward(c echo.Context) {
|
||||||
|
|
||||||
|
}
|
@@ -1,27 +1,68 @@
|
|||||||
package cluster
|
package cluster
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/hashicorp/raft"
|
"github.com/hashicorp/raft"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Implement a FSM
|
type Store interface {
|
||||||
type fsm struct{}
|
raft.FSM
|
||||||
|
|
||||||
func NewFSM() (raft.FSM, error) {
|
ListNodes() []string
|
||||||
return &fsm{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fsm) Apply(*raft.Log) interface{} {
|
// Implement a FSM
|
||||||
|
type store struct{}
|
||||||
|
|
||||||
|
func NewStore() (Store, error) {
|
||||||
|
return &store{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) Apply(log *raft.Log) interface{} {
|
||||||
|
fmt.Printf("a log entry came in (index=%d, term=%d): %s\n", log.Index, log.Term, string(log.Data))
|
||||||
|
|
||||||
|
c := command{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(log.Data, &c)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("invalid log entry\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("op: %s\n", c.Operation)
|
||||||
|
fmt.Printf("op: %+v\n", c)
|
||||||
|
|
||||||
|
switch c.Operation {
|
||||||
|
case "addNode":
|
||||||
|
b, _ := json.Marshal(c.Data)
|
||||||
|
cmd := addNodeCommand{}
|
||||||
|
json.Unmarshal(b, &cmd)
|
||||||
|
|
||||||
|
fmt.Printf("addNode: %+v\n", cmd)
|
||||||
|
case "removeNode":
|
||||||
|
b, _ := json.Marshal(c.Data)
|
||||||
|
cmd := removeNodeCommand{}
|
||||||
|
json.Unmarshal(b, &cmd)
|
||||||
|
|
||||||
|
fmt.Printf("removeNode: %+v\n", cmd)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fsm) Snapshot() (raft.FSMSnapshot, error) {
|
func (s *store) Snapshot() (raft.FSMSnapshot, error) {
|
||||||
|
fmt.Printf("a snapshot is requested\n")
|
||||||
return &fsmSnapshot{}, nil
|
return &fsmSnapshot{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fsm) Restore(snapshot io.ReadCloser) error {
|
func (s *store) Restore(snapshot io.ReadCloser) error {
|
||||||
|
fmt.Printf("a snapshot is restored\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) ListNodes() []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,6 +21,7 @@ func (c *cluster) monitorLeadership() {
|
|||||||
|
|
||||||
var weAreLeaderCh chan struct{}
|
var weAreLeaderCh chan struct{}
|
||||||
var leaderLoop sync.WaitGroup
|
var leaderLoop sync.WaitGroup
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case isLeader := <-raftNotifyCh:
|
case isLeader := <-raftNotifyCh:
|
||||||
@@ -39,6 +40,8 @@ func (c *cluster) monitorLeadership() {
|
|||||||
}(weAreLeaderCh)
|
}(weAreLeaderCh)
|
||||||
c.logger.Info().Log("cluster leadership acquired")
|
c.logger.Info().Log("cluster leadership acquired")
|
||||||
|
|
||||||
|
c.StoreAddNode(c.id, ":8080", "foo", "bar")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if weAreLeaderCh == nil {
|
if weAreLeaderCh == nil {
|
||||||
c.logger.Error().Log("attempted to stop the leader loop while not running")
|
c.logger.Error().Log("attempted to stop the leader loop while not running")
|
||||||
@@ -87,7 +90,7 @@ func (c *cluster) leaderLoop(stopCh chan struct{}) {
|
|||||||
establishedLeader := false
|
establishedLeader := false
|
||||||
RECONCILE:
|
RECONCILE:
|
||||||
// Setup a reconciliation timer
|
// Setup a reconciliation timer
|
||||||
interval := time.After(s.config.ReconcileInterval)
|
interval := time.After(10 * time.Second)
|
||||||
|
|
||||||
// Apply a raft barrier to ensure our FSM is caught up
|
// Apply a raft barrier to ensure our FSM is caught up
|
||||||
barrier := c.raft.Barrier(time.Minute)
|
barrier := c.raft.Barrier(time.Minute)
|
||||||
@@ -98,7 +101,7 @@ RECONCILE:
|
|||||||
|
|
||||||
// Check if we need to handle initial leadership actions
|
// Check if we need to handle initial leadership actions
|
||||||
if !establishedLeader {
|
if !establishedLeader {
|
||||||
if err := c.establishLeadership(stopCtx); err != nil {
|
if err := c.establishLeadership(context.TODO()); err != nil {
|
||||||
c.logger.Error().WithError(err).Log("failed to establish leadership")
|
c.logger.Error().WithError(err).Log("failed to establish leadership")
|
||||||
// Immediately revoke leadership since we didn't successfully
|
// Immediately revoke leadership since we didn't successfully
|
||||||
// establish leadership.
|
// establish leadership.
|
||||||
@@ -130,8 +133,7 @@ WAIT:
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Periodically reconcile as long as we are the leader,
|
// Periodically reconcile as long as we are the leader
|
||||||
// or when Serf events arrive
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-stopCh:
|
case <-stopCh:
|
||||||
@@ -157,7 +159,7 @@ WAIT:
|
|||||||
// leader, which means revokeLeadership followed by an
|
// leader, which means revokeLeadership followed by an
|
||||||
// establishLeadership().
|
// establishLeadership().
|
||||||
c.revokeLeadership()
|
c.revokeLeadership()
|
||||||
err := c.establishLeadership(stopCtx)
|
err := c.establishLeadership(context.TODO())
|
||||||
errCh <- err
|
errCh <- err
|
||||||
|
|
||||||
// in case establishLeadership failed, we will try to
|
// in case establishLeadership failed, we will try to
|
||||||
@@ -187,9 +189,10 @@ WAIT:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) establishLeadership(ctx context.Context) error {
|
func (c *cluster) establishLeadership(ctx context.Context) error {
|
||||||
|
c.logger.Debug().Log("establishing leadership")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cluster) revokeLeadership() {
|
func (c *cluster) revokeLeadership() {
|
||||||
|
c.logger.Debug().Log("revoking leadership")
|
||||||
}
|
}
|
||||||
|
@@ -20,23 +20,8 @@ type hclogger struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewLogger(logger log.Logger, lvl hclog.Level) hclog.Logger {
|
func NewLogger(logger log.Logger, lvl hclog.Level) hclog.Logger {
|
||||||
level := log.Linfo
|
|
||||||
|
|
||||||
switch lvl {
|
|
||||||
case hclog.Trace:
|
|
||||||
level = log.Ldebug
|
|
||||||
case hclog.Debug:
|
|
||||||
level = log.Ldebug
|
|
||||||
case hclog.Info:
|
|
||||||
level = log.Linfo
|
|
||||||
case hclog.Warn:
|
|
||||||
level = log.Lwarn
|
|
||||||
case hclog.Error:
|
|
||||||
level = log.Lerror
|
|
||||||
}
|
|
||||||
|
|
||||||
return &hclogger{
|
return &hclogger{
|
||||||
logger: logger.WithOutput(log.NewSyncWriter(log.NewConsoleWriter(os.Stderr, level, true))),
|
logger: logger,
|
||||||
level: lvl,
|
level: lvl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -242,7 +242,7 @@ func (n *node) files() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
filesChan <- "mem:" + file.Name
|
f <- "mem:" + file.Name
|
||||||
}
|
}
|
||||||
}(filesChan)
|
}(filesChan)
|
||||||
|
|
||||||
@@ -255,7 +255,7 @@ func (n *node) files() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
filesChan <- "disk:" + file.Name
|
f <- "disk:" + file.Name
|
||||||
}
|
}
|
||||||
}(filesChan)
|
}(filesChan)
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ func (n *node) files() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
filesChan <- "rtmp:" + file.Name
|
f <- "rtmp:" + file.Name
|
||||||
}
|
}
|
||||||
}(filesChan)
|
}(filesChan)
|
||||||
}
|
}
|
||||||
@@ -288,7 +288,7 @@ func (n *node) files() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
filesChan <- "srt:" + file.Name
|
f <- "srt:" + file.Name
|
||||||
}
|
}
|
||||||
}(filesChan)
|
}(filesChan)
|
||||||
}
|
}
|
||||||
|
@@ -134,6 +134,8 @@ func (d *Config) Clone() *Config {
|
|||||||
data.Router.BlockedPrefixes = copy.Slice(d.Router.BlockedPrefixes)
|
data.Router.BlockedPrefixes = copy.Slice(d.Router.BlockedPrefixes)
|
||||||
data.Router.Routes = copy.StringMap(d.Router.Routes)
|
data.Router.Routes = copy.StringMap(d.Router.Routes)
|
||||||
|
|
||||||
|
data.Cluster = d.Cluster
|
||||||
|
|
||||||
data.vars.Transfer(&d.vars)
|
data.vars.Transfer(&d.vars)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
@@ -276,6 +278,8 @@ func (d *Config) init() {
|
|||||||
d.vars.Register(value.NewBool(&d.Cluster.Enable, false), "cluster.enable", "CORE_CLUSTER_ENABLE", nil, "Enable cluster mode", false, false)
|
d.vars.Register(value.NewBool(&d.Cluster.Enable, false), "cluster.enable", "CORE_CLUSTER_ENABLE", nil, "Enable cluster mode", false, false)
|
||||||
d.vars.Register(value.NewBool(&d.Cluster.Bootstrap, false), "cluster.bootstrap", "CORE_CLUSTER_BOOTSTRAP", nil, "Bootstrap a cluster", false, false)
|
d.vars.Register(value.NewBool(&d.Cluster.Bootstrap, false), "cluster.bootstrap", "CORE_CLUSTER_BOOTSTRAP", nil, "Bootstrap a cluster", false, false)
|
||||||
d.vars.Register(value.NewBool(&d.Cluster.Debug, false), "cluster.debug", "CORE_CLUSTER_DEBUG", nil, "Switch to debug mode, not for production", false, false)
|
d.vars.Register(value.NewBool(&d.Cluster.Debug, false), "cluster.debug", "CORE_CLUSTER_DEBUG", nil, "Switch to debug mode, not for production", false, false)
|
||||||
|
d.vars.Register(value.NewAddress(&d.Cluster.Address, ":8000"), "cluster.address", "CORE_CLUSTER_ADDRESS", nil, "Raft listen address", false, true)
|
||||||
|
d.vars.Register(value.NewString(&d.Cluster.JoinAddress, ""), "cluster.join_address", "CORE_CLUSTER_JOIN_ADDRESS", nil, "Address of a core that is part of the cluster", false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the current state of the Config for completeness and sanity. Errors are
|
// Validate validates the current state of the Config for completeness and sanity. Errors are
|
||||||
@@ -455,6 +459,13 @@ func (d *Config) Validate(resetLogs bool) {
|
|||||||
d.vars.Log("error", "metrics.interval", "must be smaller than the range")
|
d.vars.Log("error", "metrics.interval", "must be smaller than the range")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If cluster mode is enabled, we can't join and bootstrap at the same time
|
||||||
|
if d.Cluster.Enable {
|
||||||
|
if d.Cluster.Bootstrap && len(d.Cluster.JoinAddress) != 0 {
|
||||||
|
d.vars.Log("error", "cluster.join_address", "can't be set if cluster.bootstrap is enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge merges the values of the known environment variables into the configuration
|
// Merge merges the values of the known environment variables into the configuration
|
||||||
|
@@ -170,6 +170,8 @@ type Data struct {
|
|||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Bootstrap bool `json:"bootstrap"`
|
Bootstrap bool `json:"bootstrap"`
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
JoinAddress string `json:"join_address"`
|
||||||
} `json:"cluster"`
|
} `json:"cluster"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user