lobby2/nodes/main.go

177 lines
3.7 KiB
Go
Raw Normal View History

2024-12-08 01:30:07 +00:00
package nodes
import (
"encoding/json"
"fmt"
"log"
"os"
"path"
"strings"
"time"
"github.com/puzpuzpuz/xsync/v3"
)
const dumpIntervalSeconds = 120
// Base nodes structure that covers all operations above nodes.
type NodesProcessor struct {
nodes *xsync.MapOf[string, Node]
dumpPath string
garbageCollectAfterSeconds int64
}
func NewNodesProcessor(dumpPath string, garbageCollectAfterSeconds int64) *NodesProcessor {
p := &NodesProcessor{
dumpPath: dumpPath,
garbageCollectAfterSeconds: garbageCollectAfterSeconds,
nodes: xsync.NewMapOf[string, Node](),
}
err := p.Load()
if err != nil {
log.Printf("failed to load nodes: %v\n", err)
}
return p
}
func (np *NodesProcessor) DumpLoop() {
log.Println(".. starting dump loop")
time.Sleep(time.Second * dumpIntervalSeconds)
for {
log.Println(".. dumping nodes")
err := np.Dump()
if err != nil {
log.Printf("failed to dump nodes: %v\n", err)
}
time.Sleep(time.Second * dumpIntervalSeconds)
}
}
// GarbageCollection removes nodes that haven't been updated for more than garbageCollectAfterSeconds.
func (np *NodesProcessor) GarbageCollection() {
for {
np.garbageCollect()
time.Sleep(time.Second * 10)
}
}
func (np *NodesProcessor) garbageCollect() {
np.nodes.Range(func(key string, value Node) bool {
if time.Now().Unix()-value.LastUpdate > np.garbageCollectAfterSeconds {
np.nodes.Delete(value.HostName)
}
return true
})
}
// Dumps content of np.nodes to np.dumpPath.
func (np *NodesProcessor) Dump() error {
ns := []Node{}
np.nodes.Range(func(key string, value Node) bool {
ns = append(ns, value)
return true
})
2024-12-08 01:48:48 +00:00
body, err := json.MarshalIndent(ns, "", " ")
2024-12-08 01:30:07 +00:00
if err != nil {
return fmt.Errorf("failed to marshal nodes: %w", err)
}
// Create directory if it doesn't exist.
if strings.Contains(np.dumpPath, "/") {
path := np.dumpPath[:strings.LastIndex(np.dumpPath, "/")]
if len(path) != 0 {
err = os.MkdirAll(path, 0755)
if err != nil {
return fmt.Errorf("failed to create directory for nodes.json: %w", err)
}
}
}
err = os.WriteFile(np.dumpPath, body, 0644)
if err != nil {
return fmt.Errorf("failed to write nodes.json: %w", err)
}
return nil
}
// Loads content of np.dumpPath to np.nodes.
func (np *NodesProcessor) Load() error {
filePath := path.Join(np.dumpPath)
_, err := os.Stat(filePath)
if os.IsNotExist(err) {
return nil
}
body, err := os.ReadFile(np.dumpPath)
if err != nil {
return fmt.Errorf("failed to read nodes.json: %w", err)
}
np.Reset()
ns := []Node{}
err = json.Unmarshal(body, &ns)
if err != nil {
return fmt.Errorf("failed to unmarshal nodes: %w", err)
}
for _, n := range ns {
np.nodes.Store(n.HostName, n)
}
return nil
}
func (np *NodesProcessor) Reset() {
np.nodes.Clear()
}
// Returns string with Prometheus metrics
// TODO: finish this
func (np *NodesProcessor) GetMetrics() string {
return ""
}
// List returns all nodes.
func (np *NodesProcessor) List() Nodes {
nodes := Nodes{}
np.nodes.Range(func(key string, value Node) bool {
nodes = append(nodes, value)
return true
})
return nodes
}
// Get returns a node by hostname.
func (np *NodesProcessor) Get(hostname string) (Node, bool) {
node, ok := np.nodes.Load(hostname)
return node, ok
}
// Refresh updates node with hostname and labels. If it doesn't exists it's created.
func (np *NodesProcessor) Refresh(hostname string, labels Labels, kv KV) {
node, ok := np.nodes.Load(hostname)
if !ok {
node = Node{}
}
node.LastUpdate = time.Now().Unix()
node.HostName = hostname
node.Labels = labels
node.KV = kv
np.nodes.Store(hostname, node)
}
// Drop removes node with given hostname.
func (np *NodesProcessor) Drop(hostname string) {
np.nodes.Delete(hostname)
}