mirror of
				https://github.com/datarhei/core.git
				synced 2025-10-31 11:26:52 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			250 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package cluster
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/datarhei/core/v16/cluster/client"
 | |
| 	"github.com/datarhei/core/v16/cluster/proxy"
 | |
| 	"github.com/datarhei/core/v16/config"
 | |
| )
 | |
| 
 | |
| type clusterNode struct {
 | |
| 	client client.APIClient
 | |
| 
 | |
| 	id                 string
 | |
| 	address            string
 | |
| 	ips                []string
 | |
| 	version            string
 | |
| 	lastContact        time.Time
 | |
| 	lastContactErr     error
 | |
| 	lastCoreContact    time.Time
 | |
| 	lastCoreContactErr error
 | |
| 	latency            time.Duration
 | |
| 	pingLock           sync.RWMutex
 | |
| 
 | |
| 	runLock    sync.Mutex
 | |
| 	cancelPing context.CancelFunc
 | |
| 
 | |
| 	proxyNode proxy.Node
 | |
| }
 | |
| 
 | |
| func NewClusterNode(id, address string) *clusterNode {
 | |
| 	n := &clusterNode{
 | |
| 		id:      id,
 | |
| 		address: address,
 | |
| 		version: "0.0.0",
 | |
| 		client: client.APIClient{
 | |
| 			Address: address,
 | |
| 			Client: &http.Client{
 | |
| 				Timeout: 5 * time.Second,
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	if host, _, err := net.SplitHostPort(address); err == nil {
 | |
| 		if addrs, err := net.LookupHost(host); err == nil {
 | |
| 			n.ips = addrs
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if version, err := n.client.Version(); err == nil {
 | |
| 		n.version = version
 | |
| 	}
 | |
| 
 | |
| 	n.start(id)
 | |
| 
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) start(id string) error {
 | |
| 	n.runLock.Lock()
 | |
| 	defer n.runLock.Unlock()
 | |
| 
 | |
| 	if n.cancelPing != nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	ctx, cancel := context.WithCancel(context.Background())
 | |
| 	n.cancelPing = cancel
 | |
| 
 | |
| 	go n.ping(ctx)
 | |
| 	go n.pingCore(ctx)
 | |
| 
 | |
| 	n.lastCoreContactErr = fmt.Errorf("not started yet")
 | |
| 	n.lastContactErr = fmt.Errorf("not started yet")
 | |
| 
 | |
| 	address, config, err := n.CoreEssentials()
 | |
| 	n.proxyNode = proxy.NewNode(id, address, config)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		n.lastCoreContactErr = err
 | |
| 		go func(ctx context.Context) {
 | |
| 			ticker := time.NewTicker(5 * time.Second)
 | |
| 			defer ticker.Stop()
 | |
| 
 | |
| 			for {
 | |
| 				select {
 | |
| 				case <-ticker.C:
 | |
| 					address, config, err := n.CoreEssentials()
 | |
| 					n.pingLock.Lock()
 | |
| 					if err == nil {
 | |
| 						n.proxyNode.SetEssentials(address, config)
 | |
| 						n.lastCoreContactErr = nil
 | |
| 					} else {
 | |
| 						n.lastCoreContactErr = err
 | |
| 					}
 | |
| 					n.pingLock.Unlock()
 | |
| 
 | |
| 					if err == nil {
 | |
| 						return
 | |
| 					}
 | |
| 				case <-ctx.Done():
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 		}(ctx)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) Stop() error {
 | |
| 	n.runLock.Lock()
 | |
| 	defer n.runLock.Unlock()
 | |
| 
 | |
| 	if n.cancelPing == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	n.proxyNode.Disconnect()
 | |
| 
 | |
| 	n.cancelPing()
 | |
| 	n.cancelPing = nil
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) Version() string {
 | |
| 	n.pingLock.RLock()
 | |
| 	defer n.pingLock.RUnlock()
 | |
| 
 | |
| 	return n.version
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) IPs() []string {
 | |
| 	return n.ips
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) Status() (string, error) {
 | |
| 	n.pingLock.RLock()
 | |
| 	defer n.pingLock.RUnlock()
 | |
| 
 | |
| 	since := time.Since(n.lastContact)
 | |
| 	if since > 5*time.Second {
 | |
| 		return "offline", fmt.Errorf("the cluster API didn't respond for %s because: %w", since, n.lastContactErr)
 | |
| 	}
 | |
| 
 | |
| 	return "online", nil
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) CoreStatus() (string, error) {
 | |
| 	n.pingLock.RLock()
 | |
| 	defer n.pingLock.RUnlock()
 | |
| 
 | |
| 	since := time.Since(n.lastCoreContact)
 | |
| 	if since > 5*time.Second {
 | |
| 		return "offline", fmt.Errorf("the core API didn't respond for %s because: %w", since, n.lastCoreContactErr)
 | |
| 	}
 | |
| 
 | |
| 	return "online", nil
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) LastContact() time.Time {
 | |
| 	n.pingLock.RLock()
 | |
| 	defer n.pingLock.RUnlock()
 | |
| 
 | |
| 	return n.lastContact
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) CoreEssentials() (string, *config.Config, error) {
 | |
| 	address, err := n.CoreAPIAddress()
 | |
| 	if err != nil {
 | |
| 		return "", nil, err
 | |
| 	}
 | |
| 
 | |
| 	config, err := n.CoreConfig()
 | |
| 	if err != nil {
 | |
| 		return "", nil, err
 | |
| 	}
 | |
| 
 | |
| 	return address, config, nil
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) CoreConfig() (*config.Config, error) {
 | |
| 	return n.client.CoreConfig()
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) CoreAPIAddress() (string, error) {
 | |
| 	return n.client.CoreAPIAddress()
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) Proxy() proxy.Node {
 | |
| 	return n.proxyNode
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) ping(ctx context.Context) {
 | |
| 	ticker := time.NewTicker(time.Second)
 | |
| 	defer ticker.Stop()
 | |
| 
 | |
| 	for {
 | |
| 		select {
 | |
| 		case <-ticker.C:
 | |
| 			start := time.Now()
 | |
| 			version, err := n.client.Version()
 | |
| 			if err == nil {
 | |
| 				n.pingLock.Lock()
 | |
| 				n.version = version
 | |
| 				n.lastContact = time.Now()
 | |
| 				n.latency = time.Since(start)
 | |
| 				n.pingLock.Unlock()
 | |
| 			} else {
 | |
| 				n.pingLock.Lock()
 | |
| 				n.lastContactErr = err
 | |
| 				n.pingLock.Unlock()
 | |
| 			}
 | |
| 		case <-ctx.Done():
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (n *clusterNode) pingCore(ctx context.Context) {
 | |
| 	ticker := time.NewTicker(time.Second)
 | |
| 	defer ticker.Stop()
 | |
| 
 | |
| 	for {
 | |
| 		select {
 | |
| 		case <-ticker.C:
 | |
| 			_, err := n.proxyNode.IsConnected()
 | |
| 
 | |
| 			if err == nil {
 | |
| 				n.pingLock.Lock()
 | |
| 				n.lastCoreContact = time.Now()
 | |
| 				n.lastCoreContactErr = nil
 | |
| 				n.pingLock.Unlock()
 | |
| 			} else {
 | |
| 				n.pingLock.Lock()
 | |
| 				n.lastCoreContactErr = fmt.Errorf("not connected to core api: %w", err)
 | |
| 				n.pingLock.Unlock()
 | |
| 			}
 | |
| 		case <-ctx.Done():
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 | 
