573 lines
21 KiB
C#
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");
|
|
}
|
|
}
|