|
|
@@ -0,0 +1,211 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "fmt"
|
|
|
+ "log"
|
|
|
+ "os"
|
|
|
+ "os/signal"
|
|
|
+ "strings"
|
|
|
+ "syscall"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "igit.com/xbase/raft"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ ColorReset = "\033[0m"
|
|
|
+ ColorDim = "\033[90m" // Dark Gray
|
|
|
+ ColorRed = "\033[31m"
|
|
|
+ ColorGreen = "\033[32m"
|
|
|
+ ColorYellow = "\033[33m"
|
|
|
+ ColorBlue = "\033[34m"
|
|
|
+ ColorCyan = "\033[36m"
|
|
|
+)
|
|
|
+
|
|
|
+func main() {
|
|
|
+ // Configuration
|
|
|
+ nodeID := "node1"
|
|
|
+ addr := "localhost:5001"
|
|
|
+ dataDir := "data/node1"
|
|
|
+
|
|
|
+ // Cluster configuration
|
|
|
+ clusterNodes := map[string]string{
|
|
|
+ "node1": "localhost:5001",
|
|
|
+ "node2": "localhost:5002",
|
|
|
+ "node3": "localhost:5003",
|
|
|
+ }
|
|
|
+
|
|
|
+ config := raft.DefaultConfig()
|
|
|
+ config.NodeID = nodeID
|
|
|
+ config.ListenAddr = addr
|
|
|
+ config.DataDir = dataDir
|
|
|
+ config.ClusterNodes = clusterNodes
|
|
|
+ // Log level: 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR
|
|
|
+ config.Logger = raft.NewConsoleLogger(nodeID, 1)
|
|
|
+
|
|
|
+ // Ensure data directory exists
|
|
|
+ if err := os.MkdirAll(dataDir, 0755); err != nil {
|
|
|
+ log.Fatalf("Failed to create data directory: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create KV Server
|
|
|
+ server, err := raft.NewKVServer(config)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("Failed to create server: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Start server
|
|
|
+ if err := server.Start(); err != nil {
|
|
|
+ log.Fatalf("Failed to start server: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("Node %s%s%s started on %s\n", ColorGreen, nodeID, ColorReset, addr)
|
|
|
+ fmt.Println("Commands: set <key> <val>, get <key>, del <key>")
|
|
|
+
|
|
|
+ // State Monitor Loop (Real-time status updates)
|
|
|
+ go func() {
|
|
|
+ var lastState string
|
|
|
+ var lastTerm uint64
|
|
|
+ // Initial state
|
|
|
+ stats := server.GetStats()
|
|
|
+ lastState = stats.State
|
|
|
+ lastTerm = stats.Term
|
|
|
+
|
|
|
+ ticker := time.NewTicker(100 * time.Millisecond)
|
|
|
+ defer ticker.Stop()
|
|
|
+
|
|
|
+ for range ticker.C {
|
|
|
+ stats := server.GetStats()
|
|
|
+ if stats.State != lastState || stats.Term != lastTerm {
|
|
|
+ fmt.Printf("\n%s[State Change] %s (Term %d) -> %s (Term %d)%s\n> ",
|
|
|
+ ColorYellow, lastState, lastTerm, stats.State, stats.Term, ColorReset)
|
|
|
+ lastState = stats.State
|
|
|
+ lastTerm = stats.Term
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ // Simple test loop
|
|
|
+ go func() {
|
|
|
+ ticker := time.NewTicker(10 * time.Second)
|
|
|
+ defer ticker.Stop()
|
|
|
+ for range ticker.C {
|
|
|
+ fmt.Println() // Extra newline
|
|
|
+ if server.IsLeader() {
|
|
|
+ fmt.Printf("--- I am the %sLEADER%s ---\n", ColorGreen, ColorReset)
|
|
|
+ } else {
|
|
|
+ leaderID := server.GetLeaderID()
|
|
|
+ fmt.Printf("--- I am %sFOLLOWER%s (Leader: %s%s%s) ---\n",
|
|
|
+ ColorYellow, ColorReset, ColorCyan, leaderID, ColorReset)
|
|
|
+ }
|
|
|
+
|
|
|
+ // We always try to Set and Get, letting the server handle routing
|
|
|
+ // Write a value
|
|
|
+ key := fmt.Sprintf("key-%d", time.Now().Unix())
|
|
|
+ val := fmt.Sprintf("val-%s", nodeID)
|
|
|
+
|
|
|
+ err := server.Set(key, val)
|
|
|
+
|
|
|
+ if server.IsLeader() {
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("%sSet failed:%s %v\n", ColorRed, ColorReset, err)
|
|
|
+ } else {
|
|
|
+ fmt.Printf("%sSet%s %s%s%s%s%s=%s%s%s%s\n",
|
|
|
+ ColorGreen, ColorReset,
|
|
|
+ ColorCyan, key, ColorReset,
|
|
|
+ ColorDim, ":", ColorReset,
|
|
|
+ ColorYellow, val, ColorReset)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Read it back
|
|
|
+ if v, ok, err := server.GetLinear(key); err != nil {
|
|
|
+ fmt.Printf("%sGetLinear failed:%s %v\n", ColorRed, ColorReset, err)
|
|
|
+ } else if ok {
|
|
|
+ fmt.Printf("%sGet%s %s%s%s%s%s=%s%s%s%s\n",
|
|
|
+ ColorGreen, ColorReset,
|
|
|
+ ColorCyan, key, ColorReset,
|
|
|
+ ColorDim, ":", ColorReset,
|
|
|
+ ColorYellow, v, ColorReset)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Print stats
|
|
|
+ metrics := server.GetMetrics()
|
|
|
+ health := server.HealthCheck()
|
|
|
+ fmt.Printf("Term: %s%d%s (M:%d), CommitIndex: %s%d%s, Applied: %s%d%s\n",
|
|
|
+ ColorBlue, health.Term, ColorReset, metrics.Term,
|
|
|
+ ColorGreen, health.CommitIndex, ColorReset,
|
|
|
+ ColorGreen, health.LastApplied, ColorReset)
|
|
|
+ fmt.Print("> ") // Prompt
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ // CLI Loop
|
|
|
+ go func() {
|
|
|
+ scanner := bufio.NewScanner(os.Stdin)
|
|
|
+ fmt.Print("> ")
|
|
|
+ for scanner.Scan() {
|
|
|
+ text := strings.TrimSpace(scanner.Text())
|
|
|
+ if text == "" {
|
|
|
+ fmt.Print("> ")
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ parts := strings.Fields(text)
|
|
|
+ cmd := strings.ToLower(parts[0])
|
|
|
+
|
|
|
+ switch cmd {
|
|
|
+ case "set":
|
|
|
+ if len(parts) != 3 {
|
|
|
+ fmt.Println("Usage: set <key> <value>")
|
|
|
+ break
|
|
|
+ }
|
|
|
+ key, val := parts[1], parts[2]
|
|
|
+ if err := server.Set(key, val); err != nil {
|
|
|
+ fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
|
|
|
+ } else {
|
|
|
+ fmt.Printf("%sOK%s\n", ColorGreen, ColorReset)
|
|
|
+ }
|
|
|
+ case "get":
|
|
|
+ if len(parts) != 2 {
|
|
|
+ fmt.Println("Usage: get <key>")
|
|
|
+ break
|
|
|
+ }
|
|
|
+ key := parts[1]
|
|
|
+ if val, ok, err := server.GetLinear(key); err != nil {
|
|
|
+ fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
|
|
|
+ } else if !ok {
|
|
|
+ fmt.Printf("%sNot Found%s\n", ColorYellow, ColorReset)
|
|
|
+ } else {
|
|
|
+ // Fixed: 7 verbs for 7 args
|
|
|
+ fmt.Printf("%s%s%s%s%s=%s%s\n", ColorDim, key, ColorReset, ColorDim, ":", ColorReset, val)
|
|
|
+ }
|
|
|
+ case "del", "delete":
|
|
|
+ if len(parts) != 2 {
|
|
|
+ fmt.Println("Usage: del <key>")
|
|
|
+ break
|
|
|
+ }
|
|
|
+ key := parts[1]
|
|
|
+ if err := server.Del(key); err != nil {
|
|
|
+ fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
|
|
|
+ } else {
|
|
|
+ fmt.Printf("%sDeleted%s\n", ColorGreen, ColorReset)
|
|
|
+ }
|
|
|
+ case "help":
|
|
|
+ fmt.Println("Commands: set <key> <val>, get <key>, del <key>")
|
|
|
+ default:
|
|
|
+ fmt.Println("Unknown command")
|
|
|
+ }
|
|
|
+ fmt.Print("> ")
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ // Handle shutdown
|
|
|
+ sigCh := make(chan os.Signal, 1)
|
|
|
+ signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
|
+ <-sigCh
|
|
|
+
|
|
|
+ fmt.Println("\nShutting down...")
|
|
|
+ server.Stop()
|
|
|
+}
|
|
|
+
|