Files
skystack/QemuVmManager.Console/P2PConsole.cs
2025-08-31 14:14:17 -04:00

573 lines
21 KiB
C#

using QemuVmManager.Core;
using QemuVmManager.Models;
using QemuVmManager.Services;
using Microsoft.Extensions.Logging;
namespace QemuVmManager.Console;
public class P2PConsole
{
private readonly P2PNode _p2pNode;
private readonly VmManagementService _vmService;
private readonly ILogger<P2PConsole>? _logger;
private bool _isRunning = false;
public P2PConsole(string nodeId, int port = 8080, ILogger<P2PConsole>? logger = null)
{
_p2pNode = new P2PNode(nodeId, port, logger: null);
_vmService = new VmManagementService();
_logger = logger;
// Subscribe to events
_p2pNode.RoleChanged += OnRoleChanged;
_p2pNode.NodeJoined += OnNodeJoined;
_p2pNode.NodeLeft += OnNodeLeft;
_p2pNode.VmStarted += OnVmStarted;
_p2pNode.VmStopped += OnVmStopped;
}
public async Task StartAsync()
{
if (_isRunning)
return;
System.Console.WriteLine("=== QEMU P2P Cluster Manager ===");
System.Console.WriteLine($"Node ID: {_p2pNode.CurrentNode.NodeId}");
System.Console.WriteLine($"Hostname: {_p2pNode.CurrentNode.Hostname}");
System.Console.WriteLine($"IP Address: {_p2pNode.CurrentNode.IpAddress}");
System.Console.WriteLine($"Port: {_p2pNode.CurrentNode.Port}");
System.Console.WriteLine();
System.Console.WriteLine("Type 'help' for available commands");
System.Console.WriteLine();
await _p2pNode.StartAsync();
_isRunning = true;
await RunInteractiveModeAsync();
}
public async Task StopAsync()
{
if (!_isRunning)
return;
await _p2pNode.StopAsync();
_isRunning = false;
}
private async Task RunInteractiveModeAsync()
{
while (_isRunning)
{
try
{
System.Console.Write($"p2p-{_p2pNode.CurrentNode.NodeId}> ");
var input = System.Console.ReadLine()?.Trim();
if (string.IsNullOrEmpty(input))
continue;
var parts = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var command = parts[0].ToLower();
var arguments = parts.Skip(1).ToArray();
switch (command)
{
case "help":
ShowHelp();
break;
case "status":
await ShowStatusAsync();
break;
case "cluster":
await ShowClusterAsync();
break;
case "nodes":
await ShowNodesAsync();
break;
case "vms":
await ShowVmsAsync();
break;
case "start":
await StartVmAsync(arguments);
break;
case "stop":
await StopVmAsync(arguments);
break;
case "migrate":
await MigrateVmAsync(arguments);
break;
case "forward":
await ForwardPortAsync(arguments);
break;
case "create":
await CreateVmAsync(arguments);
break;
case "list":
await ListVmsAsync();
break;
case "upnp":
await ShowUPnPStatusAsync();
break;
case "discovery":
await ShowDiscoveryInfoAsync();
break;
case "exit":
case "quit":
System.Console.WriteLine("Stopping P2P node...");
await StopAsync();
System.Console.WriteLine("Goodbye!");
return;
default:
System.Console.WriteLine($"Unknown command: {command}");
System.Console.WriteLine("Type 'help' for available commands");
break;
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error: {ex.Message}");
}
System.Console.WriteLine();
}
}
private void ShowHelp()
{
System.Console.WriteLine("Available commands:");
System.Console.WriteLine(" status - Show current node status");
System.Console.WriteLine(" cluster - Show cluster information");
System.Console.WriteLine(" nodes - List all nodes in cluster");
System.Console.WriteLine(" vms - List all VMs in cluster");
System.Console.WriteLine(" list - List local VM configurations");
System.Console.WriteLine(" create <name> - Create a new VM (interactive)");
System.Console.WriteLine(" start <name> [node-id] - Start a VM (optionally on specific node)");
System.Console.WriteLine(" stop <vm-id> - Stop a VM");
System.Console.WriteLine(" migrate <vm-id> <node-id> - Migrate VM to different node");
System.Console.WriteLine(" forward <vm-id> <port> - Forward port for VM (master only)");
System.Console.WriteLine(" upnp - Show UPnP status");
System.Console.WriteLine(" discovery - Show network discovery information");
System.Console.WriteLine(" help - Show this help");
System.Console.WriteLine(" exit/quit - Exit the application");
}
private async Task ShowStatusAsync()
{
var node = _p2pNode.CurrentNode;
System.Console.WriteLine($"=== Node Status ===");
System.Console.WriteLine($"Node ID: {node.NodeId}");
System.Console.WriteLine($"Hostname: {node.Hostname}");
System.Console.WriteLine($"IP Address: {node.IpAddress}");
System.Console.WriteLine($"Port: {node.Port}");
System.Console.WriteLine($"Role: {node.Role}");
System.Console.WriteLine($"State: {node.State}");
System.Console.WriteLine($"Is Master: {_p2pNode.IsMaster}");
System.Console.WriteLine();
System.Console.WriteLine("=== System Info ===");
var sysInfo = node.SystemInfo;
System.Console.WriteLine($"OS: {sysInfo.OsName} {sysInfo.OsVersion}");
System.Console.WriteLine($"Architecture: {sysInfo.Architecture}");
System.Console.WriteLine($"CPU Cores: {sysInfo.CpuCores}");
System.Console.WriteLine($"Total Memory: {sysInfo.TotalMemory:N0} MB");
System.Console.WriteLine($"Available Memory: {sysInfo.AvailableMemory:N0} MB");
System.Console.WriteLine($"Virtualization: {sysInfo.AvailableVirtualization}");
System.Console.WriteLine($"QEMU Installed: {sysInfo.QemuInstalled}");
System.Console.WriteLine($"QEMU Version: {sysInfo.QemuVersion}");
}
private async Task ShowClusterAsync()
{
var cluster = _p2pNode.ClusterState;
System.Console.WriteLine($"=== Cluster Information ===");
System.Console.WriteLine($"Cluster ID: {cluster.ClusterId}");
System.Console.WriteLine($"Last Updated: {cluster.LastUpdated:yyyy-MM-dd HH:mm:ss}");
System.Console.WriteLine($"Total Nodes: {cluster.Nodes.Count}");
System.Console.WriteLine($"Total VMs: {cluster.DistributedVms.Count}");
if (cluster.MasterNode != null)
{
System.Console.WriteLine();
System.Console.WriteLine("=== Master Node ===");
System.Console.WriteLine($"Node ID: {cluster.MasterNode.NodeId}");
System.Console.WriteLine($"Hostname: {cluster.MasterNode.Hostname}");
System.Console.WriteLine($"IP Address: {cluster.MasterNode.IpAddress}");
System.Console.WriteLine($"Last Seen: {cluster.MasterNode.LastSeen:yyyy-MM-dd HH:mm:ss}");
}
}
private async Task ShowNodesAsync()
{
var cluster = _p2pNode.ClusterState;
if (cluster.Nodes.Count == 0)
{
System.Console.WriteLine("No nodes in cluster.");
return;
}
System.Console.WriteLine($"{"Node ID",-20} {"Hostname",-15} {"IP Address",-15} {"Role",-10} {"State",-10} {"Last Seen"}");
System.Console.WriteLine(new string('-', 90));
foreach (var node in cluster.Nodes)
{
var lastSeen = node.LastSeen.ToString("yyyy-MM-dd HH:mm:ss");
System.Console.WriteLine($"{node.NodeId,-20} {node.Hostname,-15} {node.IpAddress,-15} {node.Role,-10} {node.State,-10} {lastSeen}");
}
}
private async Task ShowVmsAsync()
{
var cluster = _p2pNode.ClusterState;
if (cluster.DistributedVms.Count == 0)
{
System.Console.WriteLine("No VMs in cluster.");
return;
}
System.Console.WriteLine($"{"VM ID",-36} {"Name",-20} {"Node ID",-20} {"State",-10} {"Started"}");
System.Console.WriteLine(new string('-', 100));
foreach (var vm in cluster.DistributedVms.Values)
{
var started = vm.StartedAt.ToString("yyyy-MM-dd HH:mm:ss");
System.Console.WriteLine($"{vm.VmId,-36} {vm.VmName,-20} {vm.NodeId,-20} {vm.State,-10} {started}");
if (vm.PublicEndpoint != null)
{
System.Console.WriteLine($" Public: {vm.PublicEndpoint.PublicIp}:{vm.PublicEndpoint.PublicPort}");
}
}
}
private async Task ListVmsAsync()
{
var configs = _vmService.GetAllVmConfigurations().ToList();
if (configs.Count == 0)
{
System.Console.WriteLine("No VM configurations found.");
return;
}
System.Console.WriteLine($"{"Name",-20} {"CPU",-8} {"Memory",-10} {"Description"}");
System.Console.WriteLine(new string('-', 70));
foreach (var config in configs)
{
var cpuText = $"{config.Cpu.Cores} cores";
var memoryText = $"{config.Memory.Size}{config.Memory.Unit}";
System.Console.WriteLine($"{config.Name,-20} {cpuText,-8} {memoryText,-10} {config.Description}");
}
}
private async Task CreateVmAsync(string[] arguments)
{
string vmName;
if (arguments.Length > 0)
{
vmName = arguments[0];
}
else
{
System.Console.Write("Enter VM name: ");
vmName = System.Console.ReadLine()?.Trim() ?? "";
}
if (string.IsNullOrEmpty(vmName))
{
System.Console.WriteLine("VM name cannot be empty.");
return;
}
var config = new VmConfiguration
{
Name = vmName,
Description = GetUserInput("Description (optional): "),
Cpu = new CpuConfiguration
{
Cores = int.Parse(GetUserInput("CPU cores (2): ", "2")),
Model = GetUserInput("CPU model (qemu64): ", "qemu64")
},
Memory = new MemoryConfiguration
{
Size = long.Parse(GetUserInput("Memory size in MB (2048): ", "2048")),
Unit = "M"
},
Storage = new StorageConfiguration
{
Disks = new List<DiskConfiguration>
{
new DiskConfiguration
{
Path = GetUserInput($"Disk path (vm-disks/{vmName}.qcow2): ", $"vm-disks/{vmName}.qcow2"),
Size = long.Parse(GetUserInput("Disk size in GB (10): ", "10")),
Format = GetUserInput("Disk format (qcow2): ", "qcow2"),
Interface = GetUserInput("Disk interface (virtio): ", "virtio"),
IsBoot = true
}
}
},
Network = new NetworkConfiguration
{
Interfaces = new List<NetworkInterfaceConfiguration>
{
new NetworkInterfaceConfiguration
{
Type = "user",
Model = "e1000",
Mac = GetUserInput("MAC address (auto): ", "")
}
}
},
Display = new DisplayConfiguration
{
Type = GetUserInput("Display type (gtk): ", "gtk"),
Vga = GetUserInput("VGA type (virtio): ", "virtio")
}
};
await _vmService.CreateVmAsync(config);
System.Console.WriteLine($"VM '{vmName}' created successfully.");
}
private async Task StartVmAsync(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: start <vm-name> [node-id]");
return;
}
var vmName = arguments[0];
var targetNodeId = arguments.Length > 1 ? arguments[1] : null;
try
{
var config = _vmService.GetVmConfiguration(vmName);
if (config == null)
{
System.Console.WriteLine($"VM configuration '{vmName}' not found.");
return;
}
var vm = await _p2pNode.StartVmAsync(config, targetNodeId);
System.Console.WriteLine($"VM '{vmName}' started successfully with ID: {vm.VmId}");
if (targetNodeId != null)
{
System.Console.WriteLine($"VM is running on node: {targetNodeId}");
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Failed to start VM: {ex.Message}");
}
}
private async Task StopVmAsync(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: stop <vm-id>");
return;
}
var vmId = arguments[0];
try
{
var success = await _p2pNode.StopVmAsync(vmId);
if (success)
{
System.Console.WriteLine($"VM '{vmId}' stopped successfully.");
}
else
{
System.Console.WriteLine($"Failed to stop VM '{vmId}'.");
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error stopping VM: {ex.Message}");
}
}
private async Task MigrateVmAsync(string[] arguments)
{
if (arguments.Length < 2)
{
System.Console.WriteLine("Usage: migrate <vm-id> <target-node-id>");
return;
}
var vmId = arguments[0];
var targetNodeId = arguments[1];
try
{
var response = await _p2pNode.MigrateVmAsync(vmId, targetNodeId);
if (response.Success)
{
System.Console.WriteLine($"VM '{vmId}' migrated to node '{targetNodeId}' successfully.");
}
else
{
System.Console.WriteLine($"Failed to migrate VM: {response.ErrorMessage}");
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error migrating VM: {ex.Message}");
}
}
private async Task ForwardPortAsync(string[] arguments)
{
if (arguments.Length < 2)
{
System.Console.WriteLine("Usage: forward <vm-id> <private-port> [public-port]");
return;
}
var vmId = arguments[0];
var privatePort = int.Parse(arguments[1]);
var publicPort = arguments.Length > 2 ? int.Parse(arguments[2]) : (int?)null;
try
{
var response = await _p2pNode.RequestPortForwardingAsync(vmId, privatePort, publicPort);
if (response.Success)
{
System.Console.WriteLine($"Port forwarding created successfully.");
System.Console.WriteLine($"Public IP: {response.PublicIp}");
System.Console.WriteLine($"Public Port: {response.PublicPort}");
System.Console.WriteLine($"Private Port: {privatePort}");
}
else
{
System.Console.WriteLine($"Failed to create port forwarding: {response.ErrorMessage}");
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error creating port forwarding: {ex.Message}");
}
}
private async Task ShowUPnPStatusAsync()
{
try
{
var upnpManager = new UPnPManager();
var isAvailable = await upnpManager.IsUPnPAvailableAsync();
var externalIp = await upnpManager.GetExternalIpAddressAsync();
var mappings = await upnpManager.GetPortMappingsAsync();
System.Console.WriteLine("=== UPnP Status ===");
System.Console.WriteLine($"Available: {isAvailable}");
System.Console.WriteLine($"External IP: {externalIp}");
System.Console.WriteLine($"Active Mappings: {mappings.Count}");
if (mappings.Count > 0)
{
System.Console.WriteLine();
System.Console.WriteLine("=== Port Mappings ===");
System.Console.WriteLine($"{"External Port",-15} {"Internal Port",-15} {"Protocol",-10} {"Description"}");
System.Console.WriteLine(new string('-', 70));
foreach (var mapping in mappings)
{
System.Console.WriteLine($"{mapping.ExternalPort,-15} {mapping.InternalPort,-15} {mapping.Protocol,-10} {mapping.Description}");
}
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error checking UPnP status: {ex.Message}");
}
}
private void OnRoleChanged(object? sender, NodeRole role)
{
System.Console.WriteLine($"Role changed to: {role}");
if (role == NodeRole.Master)
{
System.Console.WriteLine("🎉 This node is now the MASTER!");
}
}
private void OnNodeJoined(object? sender, NodeInfo node)
{
System.Console.WriteLine($"Node joined: {node.NodeId} ({node.IpAddress})");
}
private void OnNodeLeft(object? sender, NodeInfo node)
{
System.Console.WriteLine($"Node left: {node.NodeId}");
}
private void OnVmStarted(object? sender, VmInstance vm)
{
System.Console.WriteLine($"VM started: {vm.VmName} (ID: {vm.VmId}) on node {vm.NodeId}");
}
private void OnVmStopped(object? sender, VmInstance vm)
{
System.Console.WriteLine($"VM stopped: {vm.VmName} (ID: {vm.VmId})");
}
private string GetUserInput(string prompt, string defaultValue = "")
{
System.Console.Write(prompt);
var input = System.Console.ReadLine()?.Trim();
return string.IsNullOrEmpty(input) ? defaultValue : input;
}
private async Task ShowDiscoveryInfoAsync()
{
System.Console.WriteLine("=== Network Discovery Information ===");
System.Console.WriteLine($"Current Node ID: {_p2pNode.CurrentNode.NodeId}");
System.Console.WriteLine($"Current Node IP: {_p2pNode.CurrentNode.IpAddress}");
System.Console.WriteLine($"Current Node Port: {_p2pNode.CurrentNode.Port}");
System.Console.WriteLine($"Current Role: {_p2pNode.CurrentNode.Role}");
System.Console.WriteLine($"Is Master: {_p2pNode.IsMaster}");
System.Console.WriteLine();
// Get cluster state to show known nodes
var cluster = _p2pNode.ClusterState;
System.Console.WriteLine($"Known Nodes: {cluster.Nodes.Count}");
if (cluster.Nodes.Count > 0)
{
System.Console.WriteLine();
System.Console.WriteLine("=== Known Nodes ===");
System.Console.WriteLine($"{"Node ID",-20} {"IP Address",-15} {"Port",-8} {"Role",-10} {"Last Seen"}");
System.Console.WriteLine(new string('-', 80));
foreach (var node in cluster.Nodes)
{
var lastSeen = node.LastSeen.ToString("HH:mm:ss");
System.Console.WriteLine($"{node.NodeId,-20} {node.IpAddress,-15} {node.Port,-8} {node.Role,-10} {lastSeen}");
}
}
else
{
System.Console.WriteLine("No other nodes discovered yet.");
System.Console.WriteLine();
System.Console.WriteLine("Discovery troubleshooting:");
System.Console.WriteLine("1. Make sure other nodes are running on the same network");
System.Console.WriteLine("2. Check if firewall is blocking UDP port 8080");
System.Console.WriteLine("3. Try running multiple instances on different machines");
System.Console.WriteLine("4. Check network connectivity between nodes");
}
System.Console.WriteLine();
System.Console.WriteLine("=== Network Configuration ===");
System.Console.WriteLine("UDP Discovery: Enabled (port 8080)");
System.Console.WriteLine("TCP Communication: Enabled (port 8081)");
System.Console.WriteLine("Heartbeat Interval: 5 seconds");
System.Console.WriteLine("Discovery Interval: 30 seconds");
System.Console.WriteLine("Node Timeout: 30 seconds");
}
}