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 := "node3" addr := "localhost:5003" dataDir := "data/node3" // 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 , get , del ") // 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 ") 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 ") 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 ") 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 , get , del ") 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() }