mirror of
https://github.com/datarhei/core.git
synced 2025-10-27 01:41:00 +08:00
Replace CORE_CLUSTER_JOIN_ADDRESS with CORE_CLUSTER_PEERS. This is a comma separated list of cluster members with their IDs of the form ID@host:port On startup the node tries to connect to all the peers. In case of sudden deaths of a node this will allow to find back into the cluster. The list in CLUSTER_PEERS is a starting point of known peers. Other node that are not in that list can still join the cluster. File and stream proxy has been moved to the Proxy type.
223 lines
4.0 KiB
Go
223 lines
4.0 KiB
Go
package cluster
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
httpapi "github.com/datarhei/core/v16/http/api"
|
|
"github.com/datarhei/core/v16/log"
|
|
)
|
|
|
|
// Forwarder forwards any HTTP request from a follower to the leader
|
|
type Forwarder interface {
|
|
SetLeader(address string)
|
|
HasLeader() bool
|
|
Join(origin, id, raftAddress, apiAddress, peerAddress string) error
|
|
Leave(origin, id string) error
|
|
Snapshot() ([]byte, error)
|
|
AddProcess() error
|
|
UpdateProcess() error
|
|
RemoveProcess() error
|
|
}
|
|
|
|
type forwarder struct {
|
|
id string
|
|
leaderAddr string
|
|
lock sync.RWMutex
|
|
|
|
client *http.Client
|
|
|
|
logger log.Logger
|
|
}
|
|
|
|
type ForwarderConfig struct {
|
|
ID string
|
|
Logger log.Logger
|
|
}
|
|
|
|
func NewForwarder(config ForwarderConfig) (Forwarder, error) {
|
|
f := &forwarder{
|
|
id: config.ID,
|
|
logger: config.Logger,
|
|
}
|
|
|
|
if f.logger == nil {
|
|
f.logger = log.New("")
|
|
}
|
|
|
|
tr := &http.Transport{
|
|
MaxIdleConns: 10,
|
|
IdleConnTimeout: 30 * time.Second,
|
|
}
|
|
|
|
client := &http.Client{
|
|
Transport: tr,
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
f.client = client
|
|
|
|
return f, nil
|
|
}
|
|
|
|
func (f *forwarder) SetLeader(address string) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
|
|
if f.leaderAddr == address {
|
|
return
|
|
}
|
|
|
|
f.logger.Debug().Log("setting leader address to %s", address)
|
|
|
|
f.leaderAddr = address
|
|
}
|
|
|
|
func (f *forwarder) HasLeader() bool {
|
|
return len(f.leaderAddr) != 0
|
|
}
|
|
|
|
func (f *forwarder) Join(origin, id, raftAddress, apiAddress, peerAddress string) error {
|
|
if origin == "" {
|
|
origin = f.id
|
|
}
|
|
|
|
r := JoinRequest{
|
|
Origin: origin,
|
|
ID: id,
|
|
RaftAddress: raftAddress,
|
|
APIAddress: apiAddress,
|
|
}
|
|
|
|
f.logger.Debug().WithField("request", r).Log("forwarding to leader")
|
|
|
|
data, err := json.Marshal(&r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = f.call(http.MethodPost, "/join", "application/json", bytes.NewReader(data), peerAddress)
|
|
|
|
return err
|
|
}
|
|
|
|
func (f *forwarder) Leave(origin, id string) error {
|
|
if origin == "" {
|
|
origin = f.id
|
|
}
|
|
|
|
r := LeaveRequest{
|
|
Origin: origin,
|
|
ID: id,
|
|
}
|
|
|
|
f.logger.Debug().WithField("request", r).Log("forwarding to leader")
|
|
|
|
data, err := json.Marshal(&r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = f.call(http.MethodPost, "/leave", "application/json", bytes.NewReader(data), "")
|
|
|
|
return err
|
|
}
|
|
|
|
func (f *forwarder) Snapshot() ([]byte, error) {
|
|
r, err := f.stream(http.MethodGet, "/snapshot", "", nil, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func (f *forwarder) AddProcess() error {
|
|
return fmt.Errorf("not implemented")
|
|
}
|
|
|
|
func (f *forwarder) UpdateProcess() error {
|
|
return fmt.Errorf("not implemented")
|
|
}
|
|
|
|
func (f *forwarder) RemoveProcess() error {
|
|
return fmt.Errorf("not implemented")
|
|
}
|
|
|
|
func (f *forwarder) stream(method, path, contentType string, data io.Reader, leaderOverride string) (io.ReadCloser, error) {
|
|
leaderAddr := f.leaderAddr
|
|
if len(leaderOverride) != 0 {
|
|
leaderAddr = leaderOverride
|
|
}
|
|
|
|
if len(leaderAddr) == 0 {
|
|
return nil, fmt.Errorf("no leader address defined")
|
|
}
|
|
|
|
f.lock.RLock()
|
|
address := "http://" + leaderAddr + "/v1" + path
|
|
f.lock.RUnlock()
|
|
|
|
f.logger.Debug().Log("address: %s", address)
|
|
|
|
req, err := http.NewRequest(method, address, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if method == "POST" || method == "PUT" {
|
|
req.Header.Add("Content-Type", contentType)
|
|
}
|
|
|
|
status, body, err := f.request(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if status < 200 || status >= 300 {
|
|
e := httpapi.Error{}
|
|
|
|
defer body.Close()
|
|
|
|
x, _ := io.ReadAll(body)
|
|
|
|
json.Unmarshal(x, &e)
|
|
|
|
return nil, e
|
|
}
|
|
|
|
return body, nil
|
|
}
|
|
|
|
func (f *forwarder) call(method, path, contentType string, data io.Reader, leaderOverride string) ([]byte, error) {
|
|
body, err := f.stream(method, path, contentType, data, leaderOverride)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer body.Close()
|
|
|
|
x, _ := io.ReadAll(body)
|
|
|
|
return x, nil
|
|
}
|
|
|
|
func (f *forwarder) request(req *http.Request) (int, io.ReadCloser, error) {
|
|
resp, err := f.client.Do(req)
|
|
if err != nil {
|
|
return -1, nil, err
|
|
}
|
|
|
|
return resp.StatusCode, resp.Body, nil
|
|
}
|