feat: mock gateway
This commit is contained in:
274
internal/mock/discovery.go
Normal file
274
internal/mock/discovery.go
Normal file
@@ -0,0 +1,274 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"spore-gateway/internal/discovery"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// MockNodeDiscovery simulates node discovery with mock nodes
|
||||
type MockNodeDiscovery struct {
|
||||
mockNodes map[string]*discovery.NodeInfo
|
||||
primaryNode string
|
||||
mutex sync.RWMutex
|
||||
callbacks []discovery.NodeUpdateCallback
|
||||
heartbeatRate time.Duration
|
||||
shutdownChan chan struct{}
|
||||
shutdownOnce sync.Once
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewMockNodeDiscovery creates a new mock node discovery instance
|
||||
func NewMockNodeDiscovery(numNodes int, heartbeatRate time.Duration) *MockNodeDiscovery {
|
||||
mnd := &MockNodeDiscovery{
|
||||
mockNodes: make(map[string]*discovery.NodeInfo),
|
||||
heartbeatRate: heartbeatRate,
|
||||
shutdownChan: make(chan struct{}),
|
||||
logger: log.New(),
|
||||
}
|
||||
|
||||
// Generate mock nodes with realistic firmware versions
|
||||
// These versions should match the firmware available in the registry
|
||||
now := time.Now()
|
||||
for i := 0; i < numNodes; i++ {
|
||||
ip := fmt.Sprintf("192.168.1.%d", 100+i)
|
||||
hostname := fmt.Sprintf("spore-node-%d", i+1)
|
||||
|
||||
// Distribute nodes across different firmware versions
|
||||
// Most nodes on stable versions, some on beta
|
||||
var version string
|
||||
switch i % 5 {
|
||||
case 0, 1:
|
||||
version = "1.0.0" // 40% on oldest stable
|
||||
case 2, 3:
|
||||
version = "1.1.0" // 40% on newer stable
|
||||
case 4:
|
||||
version = "1.2.0" // 20% on beta
|
||||
}
|
||||
|
||||
// Determine stability based on version
|
||||
stable := "true"
|
||||
env := "production"
|
||||
if version == "1.2.0" {
|
||||
stable = "false"
|
||||
env = "beta"
|
||||
}
|
||||
|
||||
nodeInfo := &discovery.NodeInfo{
|
||||
IP: ip,
|
||||
Port: 80,
|
||||
Hostname: hostname,
|
||||
Status: discovery.NodeStatusActive,
|
||||
DiscoveredAt: now.Add(-time.Duration(i*5) * time.Minute),
|
||||
LastSeen: now,
|
||||
Uptime: fmt.Sprintf("%dh%dm", 10+i, rand.Intn(60)),
|
||||
Labels: map[string]string{
|
||||
"version": version,
|
||||
"stable": stable,
|
||||
"env": env,
|
||||
"zone": fmt.Sprintf("zone-%d", (i%3)+1),
|
||||
"type": "spore-node",
|
||||
},
|
||||
Latency: int64(10 + rand.Intn(50)),
|
||||
Resources: map[string]interface{}{
|
||||
"freeHeap": 32768 + rand.Intn(32768),
|
||||
"cpuFreqMHz": 80 + rand.Intn(160),
|
||||
"flashChipSize": 4194304,
|
||||
},
|
||||
}
|
||||
|
||||
mnd.mockNodes[ip] = nodeInfo
|
||||
}
|
||||
|
||||
// Set first node as primary
|
||||
if numNodes > 0 {
|
||||
mnd.primaryNode = fmt.Sprintf("192.168.1.%d", 100)
|
||||
}
|
||||
|
||||
mnd.logger.WithField("nodes", numNodes).Info("Mock discovery initialized with nodes")
|
||||
return mnd
|
||||
}
|
||||
|
||||
// Start starts the mock discovery (simulates periodic heartbeats)
|
||||
func (mnd *MockNodeDiscovery) Start() error {
|
||||
mnd.logger.Info("Starting mock node discovery")
|
||||
|
||||
// Simulate periodic node updates
|
||||
go mnd.simulateHeartbeats()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the mock discovery
|
||||
func (mnd *MockNodeDiscovery) Shutdown(ctx context.Context) error {
|
||||
mnd.shutdownOnce.Do(func() {
|
||||
mnd.logger.Info("Shutting down mock node discovery")
|
||||
close(mnd.shutdownChan)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// simulateHeartbeats simulates periodic node heartbeats and updates
|
||||
func (mnd *MockNodeDiscovery) simulateHeartbeats() {
|
||||
ticker := time.NewTicker(mnd.heartbeatRate)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mnd.shutdownChan:
|
||||
return
|
||||
case <-ticker.C:
|
||||
mnd.updateMockNodes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateMockNodes simulates node updates (version changes, status changes, etc.)
|
||||
func (mnd *MockNodeDiscovery) updateMockNodes() {
|
||||
mnd.mutex.Lock()
|
||||
defer mnd.mutex.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
for ip, node := range mnd.mockNodes {
|
||||
// Update last seen time
|
||||
node.LastSeen = now
|
||||
|
||||
// Randomly update some metrics
|
||||
if rand.Float32() < 0.3 { // 30% chance
|
||||
node.Latency = int64(10 + rand.Intn(50))
|
||||
// Update resources map
|
||||
node.Resources["freeHeap"] = 32768 + rand.Intn(32768)
|
||||
}
|
||||
|
||||
// Occasionally notify about updates
|
||||
if rand.Float32() < 0.1 { // 10% chance
|
||||
mnd.notifyCallbacks(ip, "heartbeat")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetNodes returns a copy of all mock nodes
|
||||
func (mnd *MockNodeDiscovery) GetNodes() map[string]*discovery.NodeInfo {
|
||||
mnd.mutex.RLock()
|
||||
defer mnd.mutex.RUnlock()
|
||||
|
||||
nodes := make(map[string]*discovery.NodeInfo)
|
||||
for ip, node := range mnd.mockNodes {
|
||||
// Create a copy
|
||||
nodeCopy := *node
|
||||
nodes[ip] = &nodeCopy
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// GetPrimaryNode returns the current primary node IP
|
||||
func (mnd *MockNodeDiscovery) GetPrimaryNode() string {
|
||||
mnd.mutex.RLock()
|
||||
defer mnd.mutex.RUnlock()
|
||||
return mnd.primaryNode
|
||||
}
|
||||
|
||||
// SetPrimaryNode manually sets the primary node
|
||||
func (mnd *MockNodeDiscovery) SetPrimaryNode(ip string) error {
|
||||
mnd.mutex.Lock()
|
||||
defer mnd.mutex.Unlock()
|
||||
|
||||
if _, exists := mnd.mockNodes[ip]; !exists {
|
||||
return fmt.Errorf("node %s not found", ip)
|
||||
}
|
||||
|
||||
oldPrimary := mnd.primaryNode
|
||||
mnd.primaryNode = ip
|
||||
mnd.logger.WithFields(log.Fields{
|
||||
"old_primary": oldPrimary,
|
||||
"new_primary": ip,
|
||||
}).Info("Primary node changed")
|
||||
|
||||
mnd.notifyCallbacks(ip, "primary_changed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectRandomPrimaryNode selects a random active node as primary
|
||||
func (mnd *MockNodeDiscovery) SelectRandomPrimaryNode() string {
|
||||
mnd.mutex.Lock()
|
||||
defer mnd.mutex.Unlock()
|
||||
|
||||
if len(mnd.mockNodes) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get active nodes
|
||||
var activeNodes []string
|
||||
for ip, node := range mnd.mockNodes {
|
||||
if node.Status == discovery.NodeStatusActive {
|
||||
activeNodes = append(activeNodes, ip)
|
||||
}
|
||||
}
|
||||
|
||||
if len(activeNodes) == 0 {
|
||||
return mnd.primaryNode
|
||||
}
|
||||
|
||||
// Select random node
|
||||
randomIndex := rand.Intn(len(activeNodes))
|
||||
randomNode := activeNodes[randomIndex]
|
||||
|
||||
oldPrimary := mnd.primaryNode
|
||||
mnd.primaryNode = randomNode
|
||||
mnd.logger.WithFields(log.Fields{
|
||||
"old_primary": oldPrimary,
|
||||
"new_primary": randomNode,
|
||||
}).Info("Randomly selected new primary node")
|
||||
|
||||
mnd.notifyCallbacks(randomNode, "primary_changed")
|
||||
return randomNode
|
||||
}
|
||||
|
||||
// AddCallback registers a callback for node updates
|
||||
func (mnd *MockNodeDiscovery) AddCallback(callback discovery.NodeUpdateCallback) {
|
||||
mnd.mutex.Lock()
|
||||
defer mnd.mutex.Unlock()
|
||||
mnd.callbacks = append(mnd.callbacks, callback)
|
||||
}
|
||||
|
||||
// GetClusterStatus returns current cluster status
|
||||
func (mnd *MockNodeDiscovery) GetClusterStatus() discovery.ClusterStatus {
|
||||
mnd.mutex.RLock()
|
||||
defer mnd.mutex.RUnlock()
|
||||
|
||||
return discovery.ClusterStatus{
|
||||
PrimaryNode: mnd.primaryNode,
|
||||
TotalNodes: len(mnd.mockNodes),
|
||||
UDPPort: "4210",
|
||||
ServerRunning: true,
|
||||
}
|
||||
}
|
||||
|
||||
// notifyCallbacks notifies all registered callbacks about node changes
|
||||
func (mnd *MockNodeDiscovery) notifyCallbacks(nodeIP, action string) {
|
||||
for _, callback := range mnd.callbacks {
|
||||
go callback(nodeIP, action)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateNodeVersion simulates updating a node's version (for testing rollouts)
|
||||
func (mnd *MockNodeDiscovery) UpdateNodeVersion(ip, version string) {
|
||||
mnd.mutex.Lock()
|
||||
defer mnd.mutex.Unlock()
|
||||
|
||||
if node, exists := mnd.mockNodes[ip]; exists {
|
||||
node.Labels["version"] = version
|
||||
mnd.logger.WithFields(log.Fields{
|
||||
"ip": ip,
|
||||
"version": version,
|
||||
}).Info("Updated node version")
|
||||
mnd.notifyCallbacks(ip, "version_updated")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user