feat: handle cluster events

This commit is contained in:
2025-10-26 18:09:42 +01:00
parent f7b694854d
commit 55c3aebb3f
4 changed files with 116 additions and 8 deletions

View File

@@ -2,6 +2,7 @@ package discovery
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@@ -85,7 +86,10 @@ func (nd *NodeDiscovery) handleUDPMessage(message string, remoteAddr *net.UDPAdd
nd.logger.WithField("message", "raw:"+payload).Debug("Received raw message") nd.logger.WithField("message", "raw:"+payload).Debug("Received raw message")
}, },
"cluster/event": func(payload string, remoteAddr *net.UDPAddr) { "cluster/event": func(payload string, remoteAddr *net.UDPAddr) {
nd.logger.WithField("message", "cluster/event:"+payload).Debug("Received cluster/event message") nd.handleClusterEvent(payload, remoteAddr)
},
"cluster/broadcast": func(payload string, remoteAddr *net.UDPAddr) {
nd.handleClusterBroadcast(payload, remoteAddr)
}, },
} }
@@ -373,6 +377,55 @@ func (nd *NodeDiscovery) AddCallback(callback NodeUpdateCallback) {
nd.callbacks = append(nd.callbacks, callback) nd.callbacks = append(nd.callbacks, callback)
} }
// SetClusterEventCallback sets the callback for cluster events
func (nd *NodeDiscovery) SetClusterEventCallback(callback ClusterEventBroadcaster) {
nd.mutex.Lock()
defer nd.mutex.Unlock()
nd.clusterEventCallback = callback
}
// handleClusterEvent processes cluster/event messages
func (nd *NodeDiscovery) handleClusterEvent(payload string, remoteAddr *net.UDPAddr) {
nd.logger.WithFields(log.Fields{
"payload": payload,
"from": remoteAddr.String(),
}).Debug("Received cluster/event message")
// Forward to websocket if callback is set
if nd.clusterEventCallback != nil {
nd.clusterEventCallback.BroadcastClusterEvent("cluster/event", payload)
}
}
// handleClusterBroadcast processes cluster/broadcast messages
func (nd *NodeDiscovery) handleClusterBroadcast(payload string, remoteAddr *net.UDPAddr) {
nd.logger.WithFields(log.Fields{
"payload": payload,
"from": remoteAddr.String(),
}).Debug("Received cluster/broadcast message")
// Parse the payload JSON to extract nested event and data
var payloadData struct {
Event string `json:"event"`
Data interface{} `json:"data"`
}
if err := json.Unmarshal([]byte(payload), &payloadData); err != nil {
nd.logger.WithError(err).Error("Failed to parse cluster/broadcast payload")
return
}
nd.logger.WithFields(log.Fields{
"event": payloadData.Event,
"from": remoteAddr.String(),
}).Debug("Parsed cluster/broadcast payload")
// Forward to websocket if callback is set, mapping event to topic and data to data
if nd.clusterEventCallback != nil {
nd.clusterEventCallback.BroadcastClusterEvent(payloadData.Event, payloadData.Data)
}
}
// GetClusterStatus returns current cluster status // GetClusterStatus returns current cluster status
func (nd *NodeDiscovery) GetClusterStatus() ClusterStatus { func (nd *NodeDiscovery) GetClusterStatus() ClusterStatus {
nd.mutex.RLock() nd.mutex.RLock()

View File

@@ -41,6 +41,11 @@ type ClusterStatus struct {
// NodeUpdateCallback is called when node information changes // NodeUpdateCallback is called when node information changes
type NodeUpdateCallback func(nodeIP string, action string) type NodeUpdateCallback func(nodeIP string, action string)
// ClusterEventBroadcaster interface for broadcasting cluster events
type ClusterEventBroadcaster interface {
BroadcastClusterEvent(topic string, data interface{})
}
// NodeDiscovery manages UDP-based node discovery // NodeDiscovery manages UDP-based node discovery
type NodeDiscovery struct { type NodeDiscovery struct {
udpPort string udpPort string
@@ -48,6 +53,7 @@ type NodeDiscovery struct {
primaryNode string primaryNode string
mutex sync.RWMutex mutex sync.RWMutex
callbacks []NodeUpdateCallback callbacks []NodeUpdateCallback
clusterEventCallback ClusterEventBroadcaster
staleThreshold time.Duration staleThreshold time.Duration
logger *log.Logger logger *log.Logger
} }

View File

@@ -38,6 +38,9 @@ func NewHTTPServer(port string, nodeDiscovery *discovery.NodeDiscovery) *HTTPSer
// Initialize registry client // Initialize registry client
registryClient := registry.NewRegistryClient("http://localhost:3002") registryClient := registry.NewRegistryClient("http://localhost:3002")
// Register WebSocket server as cluster event broadcaster
nodeDiscovery.SetClusterEventCallback(wsServer)
hs := &HTTPServer{ hs := &HTTPServer{
port: port, port: port,
router: mux.NewRouter(), router: mux.NewRouter(),

View File

@@ -602,6 +602,52 @@ func (wss *WebSocketServer) GetClientCount() int {
return len(wss.clients) return len(wss.clients)
} }
// BroadcastClusterEvent sends cluster events to all connected clients
func (wss *WebSocketServer) BroadcastClusterEvent(topic string, data interface{}) {
wss.mutex.RLock()
clients := make([]*websocket.Conn, 0, len(wss.clients))
for client := range wss.clients {
clients = append(clients, client)
}
wss.mutex.RUnlock()
if len(clients) == 0 {
return
}
message := struct {
Topic string `json:"topic"`
Data interface{} `json:"data"`
Timestamp string `json:"timestamp"`
}{
Topic: topic,
Data: data,
Timestamp: time.Now().Format(time.RFC3339),
}
messageData, err := json.Marshal(message)
if err != nil {
wss.logger.WithError(err).Error("Failed to marshal cluster event")
return
}
wss.logger.WithFields(log.Fields{
"topic": topic,
"clients": len(clients),
}).Debug("Broadcasting cluster event to WebSocket clients")
// Send to all clients with write synchronization
wss.writeMutex.Lock()
defer wss.writeMutex.Unlock()
for _, client := range clients {
client.SetWriteDeadline(time.Now().Add(5 * time.Second))
if err := client.WriteMessage(websocket.TextMessage, messageData); err != nil {
wss.logger.WithError(err).Error("Failed to send cluster event to client")
}
}
}
// Shutdown gracefully shuts down the WebSocket server // Shutdown gracefully shuts down the WebSocket server
func (wss *WebSocketServer) Shutdown(ctx context.Context) error { func (wss *WebSocketServer) Shutdown(ctx context.Context) error {
wss.logger.Info("Shutting down WebSocket server") wss.logger.Info("Shutting down WebSocket server")