diff --git a/QemuVmManager.Console/P2PConsole.cs b/QemuVmManager.Console/P2PConsole.cs new file mode 100644 index 0000000..c3ae440 --- /dev/null +++ b/QemuVmManager.Console/P2PConsole.cs @@ -0,0 +1,521 @@ +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? _logger; + private bool _isRunning = false; + + public P2PConsole(string nodeId, int port = 8080, ILogger? 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 "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 - Create a new VM (interactive)"); + System.Console.WriteLine(" start [node-id] - Start a VM (optionally on specific node)"); + System.Console.WriteLine(" stop - Stop a VM"); + System.Console.WriteLine(" migrate - Migrate VM to different node"); + System.Console.WriteLine(" forward - Forward port for VM (master only)"); + System.Console.WriteLine(" upnp - Show UPnP status"); + 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 + { + 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 + { + 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 [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 "); + 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 "); + 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 [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; + } +} diff --git a/QemuVmManager.Console/Program.cs b/QemuVmManager.Console/Program.cs index d118955..46c09b2 100644 --- a/QemuVmManager.Console/Program.cs +++ b/QemuVmManager.Console/Program.cs @@ -1,34 +1,67 @@ using QemuVmManager.Services; +using QemuVmManager.Core; using QemuVmManager.Models; +using System.Linq; namespace QemuVmManager.Console; class Program { - private static VmManagementService _vmService = null!; - static async Task Main(string[] args) { - try + System.Console.WriteLine("=== QEMU VM Manager ==="); + System.Console.WriteLine("Choose mode:"); + System.Console.WriteLine("1. Single Node Mode (original)"); + System.Console.WriteLine("2. P2P Distributed Mode (new)"); + System.Console.Write("Enter choice (1 or 2): "); + + var choice = System.Console.ReadLine()?.Trim(); + + if (choice == "2") { - _vmService = new VmManagementService(); - - System.Console.WriteLine("=== QEMU VM Manager ==="); - System.Console.WriteLine("Type 'help' for available commands"); - System.Console.WriteLine(); - - await RunInteractiveMode(); + await RunP2PModeAsync(args); } - catch (Exception ex) + else { - System.Console.WriteLine($"Error: {ex.Message}"); - System.Console.WriteLine("Press any key to exit..."); - System.Console.ReadKey(); + await RunSingleNodeModeAsync(); } } - static async Task RunInteractiveMode() + static async Task RunP2PModeAsync(string[] args) { + System.Console.WriteLine(); + System.Console.WriteLine("=== P2P Distributed Mode ==="); + + var nodeId = args.Length > 0 ? args[0] : Environment.MachineName; + var port = args.Length > 1 && int.TryParse(args[1], out var p) ? p : 8080; + + System.Console.WriteLine($"Node ID: {nodeId}"); + System.Console.WriteLine($"Port: {port}"); + System.Console.WriteLine(); + + var p2pConsole = new P2PConsole(nodeId, port); + + try + { + await p2pConsole.StartAsync(); + } + catch (Exception ex) + { + System.Console.WriteLine($"Error starting P2P node: {ex.Message}"); + } + } + + static async Task RunSingleNodeModeAsync() + { + System.Console.WriteLine(); + System.Console.WriteLine("=== Single Node Mode ==="); + + var vmService = new VmManagementService(); + + System.Console.WriteLine("QEMU VM Manager - Single Node Mode"); + System.Console.WriteLine("Type 'help' for available commands"); + System.Console.WriteLine(); + while (true) { try @@ -49,60 +82,48 @@ class Program ShowHelp(); break; case "list": - await ListVms(); + await ListVmsAsync(vmService); break; case "create": - await CreateVm(arguments); + await CreateVmAsync(vmService, arguments); break; case "start": - await StartVm(arguments); + await StartVmAsync(vmService, arguments); break; case "stop": - await StopVm(arguments); + await StopVmAsync(vmService, arguments); break; case "pause": - await PauseVm(arguments); + await PauseVmAsync(vmService, arguments); break; case "resume": - await ResumeVm(arguments); + await ResumeVmAsync(vmService, arguments); break; case "delete": - await DeleteVm(arguments); - break; - case "clone": - await CloneVm(arguments); - break; - case "export": - await ExportVm(arguments); - break; - case "import": - await ImportVm(arguments); + await DeleteVmAsync(vmService, arguments); break; case "status": - await ShowVmStatus(arguments); + await ShowVmStatusAsync(vmService, arguments); break; - case "config": - await ShowVmConfig(arguments); - break; - case "disk": - await ManageDisk(arguments); - break; - case "validate": - await ValidateVm(arguments); - break; - case "diagnose": - await DiagnoseSystem(); - break; - case "monitor": - await MonitorPerformance(arguments); - break; - case "metrics": - await ShowMetrics(arguments); - break; - case "exit": - case "quit": - System.Console.WriteLine("Goodbye!"); - return; + case "disk": + await HandleDiskCommandsAsync(vmService, arguments); + break; + case "validate": + await ValidateVmAsync(vmService, arguments); + break; + case "diagnose": + await DiagnoseSystem(); + break; + case "monitor": + await MonitorPerformance(arguments); + break; + case "metrics": + await ShowMetrics(arguments); + break; + case "exit": + case "quit": + System.Console.WriteLine("Goodbye!"); + return; default: System.Console.WriteLine($"Unknown command: {command}"); System.Console.WriteLine("Type 'help' for available commands"); @@ -128,29 +149,25 @@ class Program System.Console.WriteLine(" pause - Pause a VM"); System.Console.WriteLine(" resume - Resume a VM"); System.Console.WriteLine(" delete - Delete a VM"); - System.Console.WriteLine(" clone - Clone a VM"); - System.Console.WriteLine(" export - Export VM configuration"); - System.Console.WriteLine(" import [name] - Import VM configuration"); - System.Console.WriteLine(" status [name] - Show VM status"); - System.Console.WriteLine(" config - Show VM configuration"); - System.Console.WriteLine(" disk [info|resize|convert] - Manage disk images"); - System.Console.WriteLine(" validate - Validate VM disk images"); - System.Console.WriteLine(" diagnose - Diagnose system and QEMU installation"); - System.Console.WriteLine(" monitor [start|stop|status] - Performance monitoring"); - System.Console.WriteLine(" metrics [current|history] - Show performance metrics"); - System.Console.WriteLine(" help - Show this help"); - System.Console.WriteLine(" exit/quit - Exit the application"); + System.Console.WriteLine(" status [name] - Show VM status"); + System.Console.WriteLine(" disk [info|resize|convert] - Manage disk images"); + System.Console.WriteLine(" validate - Validate VM disk images"); + System.Console.WriteLine(" diagnose - Diagnose system and QEMU installation"); + System.Console.WriteLine(" monitor [start|stop|status] - Performance monitoring"); + System.Console.WriteLine(" metrics [current|history] - Show performance metrics"); + System.Console.WriteLine(" help - Show this help"); + System.Console.WriteLine(" exit/quit - Exit the application"); } - static Task ListVms() + static async Task ListVmsAsync(VmManagementService vmService) { - var configs = _vmService.GetAllVmConfigurations().ToList(); - var statuses = _vmService.GetAllVmStatuses().ToDictionary(s => s.Name); + var configs = vmService.GetAllVmConfigurations().ToList(); + var statuses = vmService.GetAllVmStatuses().ToDictionary(s => s.Name); if (configs.Count == 0) { System.Console.WriteLine("No VMs configured."); - return Task.CompletedTask; + return; } System.Console.WriteLine($"{"Name",-20} {"Status",-10} {"CPU",-8} {"Memory",-10} {"Description"}"); @@ -165,11 +182,9 @@ class Program System.Console.WriteLine($"{config.Name,-20} {statusText,-10} {cpuText,-8} {memoryText,-10} {config.Description}"); } - - return Task.CompletedTask; } - static async Task CreateVm(string[] arguments) + static async Task CreateVmAsync(VmManagementService vmService, string[] arguments) { string vmName; if (arguments.Length > 0) @@ -202,29 +217,28 @@ class Program Size = long.Parse(GetUserInput("Memory size in MB (2048): ", "2048")), Unit = "M" }, - Storage = new StorageConfiguration - { - Disks = new List - { - 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 - } - } - }, + Storage = new StorageConfiguration + { + Disks = new List + { + 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 { new NetworkInterfaceConfiguration { - Type = "bridge", - Model = "virtio-net-pci", - Bridge = GetUserInput("Network bridge (virbr0): ", "virbr0") + Type = "user", + Model = "e1000" } } }, @@ -235,11 +249,11 @@ class Program } }; - await _vmService.CreateVmAsync(config); + await vmService.CreateVmAsync(config); System.Console.WriteLine($"VM '{vmName}' created successfully."); } - static async Task StartVm(string[] arguments) + static async Task StartVmAsync(VmManagementService vmService, string[] arguments) { if (arguments.Length == 0) { @@ -248,7 +262,7 @@ class Program } var vmName = arguments[0]; - var success = await _vmService.StartVmAsync(vmName); + var success = await vmService.StartVmAsync(vmName); if (success) System.Console.WriteLine($"VM '{vmName}' started successfully."); @@ -256,7 +270,7 @@ class Program System.Console.WriteLine($"Failed to start VM '{vmName}'."); } - static async Task StopVm(string[] arguments) + static async Task StopVmAsync(VmManagementService vmService, string[] arguments) { if (arguments.Length == 0) { @@ -267,7 +281,7 @@ class Program var vmName = arguments[0]; var force = arguments.Contains("--force"); - var success = await _vmService.StopVmAsync(vmName, force); + var success = await vmService.StopVmAsync(vmName, force); if (success) System.Console.WriteLine($"VM '{vmName}' stopped successfully."); @@ -275,7 +289,7 @@ class Program System.Console.WriteLine($"Failed to stop VM '{vmName}'."); } - static async Task PauseVm(string[] arguments) + static async Task PauseVmAsync(VmManagementService vmService, string[] arguments) { if (arguments.Length == 0) { @@ -284,7 +298,7 @@ class Program } var vmName = arguments[0]; - var success = await _vmService.PauseVmAsync(vmName); + var success = await vmService.PauseVmAsync(vmName); if (success) System.Console.WriteLine($"VM '{vmName}' paused successfully."); @@ -292,7 +306,7 @@ class Program System.Console.WriteLine($"Failed to pause VM '{vmName}'."); } - static async Task ResumeVm(string[] arguments) + static async Task ResumeVmAsync(VmManagementService vmService, string[] arguments) { if (arguments.Length == 0) { @@ -301,7 +315,7 @@ class Program } var vmName = arguments[0]; - var success = await _vmService.ResumeVmAsync(vmName); + var success = await vmService.ResumeVmAsync(vmName); if (success) System.Console.WriteLine($"VM '{vmName}' resumed successfully."); @@ -309,7 +323,7 @@ class Program System.Console.WriteLine($"Failed to resume VM '{vmName}'."); } - static async Task DeleteVm(string[] arguments) + static async Task DeleteVmAsync(VmManagementService vmService, string[] arguments) { if (arguments.Length == 0) { @@ -318,587 +332,438 @@ class Program } var vmName = arguments[0]; - - System.Console.Write($"Are you sure you want to delete VM '{vmName}'? (y/N): "); - var confirm = System.Console.ReadLine()?.Trim().ToLower(); - - if (confirm == "y" || confirm == "yes") - { - await _vmService.DeleteVmAsync(vmName); - System.Console.WriteLine($"VM '{vmName}' deleted successfully."); - } - else - { - System.Console.WriteLine("Deletion cancelled."); - } + await vmService.DeleteVmAsync(vmName); + System.Console.WriteLine($"VM '{vmName}' deleted successfully."); } - static async Task CloneVm(string[] arguments) - { - if (arguments.Length < 2) - { - System.Console.WriteLine("Usage: clone "); - return; - } - - var sourceVm = arguments[0]; - var targetVm = arguments[1]; - - var clonedConfig = await _vmService.CloneVmAsync(sourceVm, targetVm); - System.Console.WriteLine($"VM '{sourceVm}' cloned to '{targetVm}' successfully."); - } - - static async Task ExportVm(string[] arguments) - { - if (arguments.Length < 2) - { - System.Console.WriteLine("Usage: export "); - return; - } - - var vmName = arguments[0]; - var exportPath = arguments[1]; - - var exportedPath = await _vmService.ExportVmConfigurationAsync(vmName, exportPath); - System.Console.WriteLine($"VM '{vmName}' exported to '{exportedPath}' successfully."); - } - - static async Task ImportVm(string[] arguments) + static async Task ShowVmStatusAsync(VmManagementService vmService, string[] arguments) { if (arguments.Length == 0) { - System.Console.WriteLine("Usage: import [new-name]"); - return; - } - - var importPath = arguments[0]; - var newName = arguments.Length > 1 ? arguments[1] : null; - - var importedConfig = await _vmService.ImportVmConfigurationAsync(importPath, newName); - System.Console.WriteLine($"VM '{importedConfig.Name}' imported successfully."); - } - - static Task ShowVmStatus(string[] arguments) - { - if (arguments.Length == 0) - { - // Show all VM statuses - var statuses = _vmService.GetAllVmStatuses(); - + // Show status of all VMs + var statuses = vmService.GetAllVmStatuses(); if (!statuses.Any()) { System.Console.WriteLine("No VMs found."); - return Task.CompletedTask; + return; } - System.Console.WriteLine($"{"Name",-20} {"Status",-10} {"PID",-8} {"Started",-20} {"Error"}"); - System.Console.WriteLine(new string('-', 80)); + System.Console.WriteLine($"{"Name",-20} {"State",-10} {"PID",-8} {"CPU %",-8} {"Memory (MB)",-12}"); + System.Console.WriteLine(new string('-', 70)); foreach (var status in statuses) { - var pidText = status.ProcessId > 0 ? status.ProcessId.ToString() : "-"; - var startedText = status.StartedAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-"; - var errorText = status.ErrorMessage ?? ""; - - System.Console.WriteLine($"{status.Name,-20} {status.State,-10} {pidText,-8} {startedText,-20} {errorText}"); + var cpuUsage = status.ResourceUsage?.CpuUsage.ToString("F1") ?? "N/A"; + var memoryUsage = status.ResourceUsage?.MemoryUsage.ToString() ?? "N/A"; + var pid = status.ProcessId > 0 ? status.ProcessId.ToString() : "N/A"; + + System.Console.WriteLine($"{status.Name,-20} {status.State,-10} {pid,-8} {cpuUsage,-8} {memoryUsage,-12}"); } } else { - // Show specific VM status + // Show status of specific VM var vmName = arguments[0]; - var status = _vmService.GetVmStatus(vmName); + var status = vmService.GetVmStatus(vmName); if (status == null) { System.Console.WriteLine($"VM '{vmName}' not found."); - return Task.CompletedTask; + return; } - System.Console.WriteLine($"VM: {status.Name}"); - System.Console.WriteLine($"Status: {status.State}"); - System.Console.WriteLine($"Process ID: {status.ProcessId}"); + System.Console.WriteLine($"=== VM Status: {vmName} ==="); + System.Console.WriteLine($"State: {status.State}"); + System.Console.WriteLine($"Process ID: {(status.ProcessId > 0 ? status.ProcessId.ToString() : "N/A")}"); System.Console.WriteLine($"Started: {status.StartedAt}"); - System.Console.WriteLine($"Stopped: {status.StoppedAt}"); if (status.ResourceUsage != null) { + System.Console.WriteLine(); + System.Console.WriteLine("=== Resource Usage ==="); System.Console.WriteLine($"CPU Usage: {status.ResourceUsage.CpuUsage:F1}%"); System.Console.WriteLine($"Memory Usage: {status.ResourceUsage.MemoryUsage} MB"); - } - - if (!string.IsNullOrEmpty(status.ErrorMessage)) - { - System.Console.WriteLine($"Error: {status.ErrorMessage}"); + System.Console.WriteLine($"Disk Usage: {status.ResourceUsage.DiskUsage} MB"); + System.Console.WriteLine($"Network RX: {status.ResourceUsage.NetworkRx} bytes"); + System.Console.WriteLine($"Network TX: {status.ResourceUsage.NetworkTx} bytes"); } } - - return Task.CompletedTask; } - static Task ShowVmConfig(string[] arguments) + static async Task HandleDiskCommandsAsync(VmManagementService vmService, string[] arguments) { - if (arguments.Length == 0) + if (arguments.Length < 2) { - System.Console.WriteLine("Usage: config "); - return Task.CompletedTask; + System.Console.WriteLine("Usage: disk [options]"); + System.Console.WriteLine("Commands: info, resize, convert"); + return; } var vmName = arguments[0]; - var config = _vmService.GetVmConfiguration(vmName); + var command = arguments[1].ToLower(); + var options = arguments.Skip(2).ToArray(); + + var config = vmService.GetVmConfiguration(vmName); + if (config == null) + { + System.Console.WriteLine($"VM '{vmName}' not found."); + return; + } + + var diskManager = new DiskManager(); + + switch (command) + { + case "info": + foreach (var disk in config.Storage.Disks) + { + var info = await diskManager.GetDiskInfoAsync(disk.Path, disk.Format); + if (info != null) + { + System.Console.WriteLine($"=== Disk: {disk.Path} ==="); + System.Console.WriteLine($"Format: {info.Format}"); + System.Console.WriteLine($"Virtual Size: {info.VirtualSize}"); + System.Console.WriteLine($"Disk Size: {info.DiskSize}"); + } + } + break; + + case "resize": + if (options.Length == 0) + { + System.Console.WriteLine("Usage: disk resize "); + return; + } + + var newSize = long.Parse(options[0]); + foreach (var disk in config.Storage.Disks) + { + var success = await diskManager.ResizeDiskAsync(disk.Path, disk.Format, newSize); + if (success) + System.Console.WriteLine($"Resized disk {disk.Path} to {newSize} bytes"); + else + System.Console.WriteLine($"Failed to resize disk {disk.Path}"); + } + break; + + case "convert": + if (options.Length == 0) + { + System.Console.WriteLine("Usage: disk convert [output-path]"); + return; + } + + var newFormat = options[0]; + var outputPath = options.Length > 1 ? options[1] : null; + + foreach (var disk in config.Storage.Disks) + { + var targetPath = outputPath ?? disk.Path.Replace(Path.GetExtension(disk.Path), $".{newFormat}"); + var success = await diskManager.ConvertDiskAsync(disk.Path, disk.Format, targetPath, newFormat); + if (success) + System.Console.WriteLine($"Converted disk {disk.Path} to {targetPath}"); + else + System.Console.WriteLine($"Failed to convert disk {disk.Path}"); + } + break; + + default: + System.Console.WriteLine($"Unknown disk command: {command}"); + break; + } + } + + static async Task ValidateVmAsync(VmManagementService vmService, string[] arguments) + { + if (arguments.Length == 0) + { + System.Console.WriteLine("Usage: validate "); + return; + } + + var vmName = arguments[0]; + var config = vmService.GetVmConfiguration(vmName); if (config == null) { System.Console.WriteLine($"VM '{vmName}' not found."); - return Task.CompletedTask; + return; } - System.Console.WriteLine($"VM Configuration: {config.Name}"); - System.Console.WriteLine($"Description: {config.Description}"); - System.Console.WriteLine($"Created: {config.Created}"); - System.Console.WriteLine($"Modified: {config.LastModified}"); - System.Console.WriteLine(); + System.Console.WriteLine($"=== Validating VM: {vmName} ==="); - System.Console.WriteLine("CPU Configuration:"); - System.Console.WriteLine($" Cores: {config.Cpu.Cores}"); - System.Console.WriteLine($" Model: {config.Cpu.Model}"); - System.Console.WriteLine($" KVM: {config.Cpu.EnableKvm}"); - System.Console.WriteLine(); - - System.Console.WriteLine("Memory Configuration:"); - System.Console.WriteLine($" Size: {config.Memory.Size}{config.Memory.Unit}"); - System.Console.WriteLine(); - - System.Console.WriteLine("Storage Configuration:"); + var diskManager = new DiskManager(); + var isValid = true; + + // Validate disks foreach (var disk in config.Storage.Disks) { - System.Console.WriteLine($" Disk: {disk.Path}"); - System.Console.WriteLine($" Size: {disk.Size} GB"); - System.Console.WriteLine($" Format: {disk.Format}"); - System.Console.WriteLine($" Interface: {disk.Interface}"); - System.Console.WriteLine($" Boot: {disk.IsBoot}"); - } - - if (!string.IsNullOrEmpty(config.Storage.Cdrom)) - { - System.Console.WriteLine($" CD-ROM: {config.Storage.Cdrom}"); - } - System.Console.WriteLine(); - - System.Console.WriteLine("Network Configuration:"); - foreach (var nic in config.Network.Interfaces) - { - System.Console.WriteLine($" Interface: {nic.Model}"); - System.Console.WriteLine($" Type: {nic.Type}"); - System.Console.WriteLine($" Bridge: {nic.Bridge}"); - if (!string.IsNullOrEmpty(nic.Mac)) + System.Console.WriteLine($"Checking disk: {disk.Path}"); + + if (!File.Exists(disk.Path)) { - System.Console.WriteLine($" MAC: {nic.Mac}"); + System.Console.WriteLine($" ❌ Disk file not found: {disk.Path}"); + isValid = false; + continue; + } + + try + { + var info = await diskManager.GetDiskInfoAsync(disk.Path, disk.Format); + if (info != null) + { + System.Console.WriteLine($" ✅ Disk format: {info.Format}"); + System.Console.WriteLine($" ✅ Virtual size: {info.VirtualSize:N0} bytes"); + } + else + { + System.Console.WriteLine($" ❌ Invalid disk format: {disk.Path}"); + isValid = false; + } + } + catch (Exception ex) + { + System.Console.WriteLine($" ❌ Error reading disk: {ex.Message}"); + isValid = false; } } + + // Validate CD-ROM if specified + if (!string.IsNullOrEmpty(config.Storage.Cdrom)) + { + System.Console.WriteLine($"Checking CD-ROM: {config.Storage.Cdrom}"); + if (File.Exists(config.Storage.Cdrom)) + { + System.Console.WriteLine(" ✅ CD-ROM file found"); + } + else + { + System.Console.WriteLine(" ❌ CD-ROM file not found"); + isValid = false; + } + } + + if (isValid) + { + System.Console.WriteLine($"✅ VM '{vmName}' validation passed"); + } + else + { + System.Console.WriteLine($"❌ VM '{vmName}' validation failed"); + } + } + + static async Task DiagnoseSystem() + { + System.Console.WriteLine("=== System Diagnosis ==="); System.Console.WriteLine(); - System.Console.WriteLine("Display Configuration:"); - System.Console.WriteLine($" Type: {config.Display.Type}"); - System.Console.WriteLine($" VGA: {config.Display.Vga}"); - System.Console.WriteLine($" SPICE: {config.Display.EnableSpice}"); - if (config.Display.EnableSpice) + // .NET Runtime + System.Console.WriteLine("1. .NET Runtime:"); + System.Console.WriteLine($" Version: {Environment.Version}"); + System.Console.WriteLine($" OS: {Environment.OSVersion}"); + System.Console.WriteLine($" Architecture: {Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")}"); + System.Console.WriteLine(); + + // QEMU Installation + System.Console.WriteLine("2. QEMU Installation:"); + var qemuManager = new QemuProcessManager(); + var isInstalled = qemuManager.IsQemuInstalled(); + var version = qemuManager.GetQemuVersion(); + var virtualization = qemuManager.GetAvailableVirtualization(); + var virtualizationEnabled = qemuManager.IsVirtualizationEnabled(); + + System.Console.WriteLine($" Installed: {(isInstalled ? "Yes" : "No")}"); + if (isInstalled) { - System.Console.WriteLine($" SPICE Port: {config.Display.SpicePort}"); + System.Console.WriteLine($" Version: {version}"); + } + System.Console.WriteLine($" Virtualization Enabled in BIOS: {(virtualizationEnabled ? "Yes" : "No")}"); + System.Console.WriteLine($" Available Virtualization: {virtualization}"); + + if (!virtualizationEnabled) + { + System.Console.WriteLine(" ⚠️ Virtualization is not enabled in BIOS/UEFI"); + System.Console.WriteLine(" Please enable VT-x (Intel) or AMD-V (AMD) in your BIOS settings"); + System.Console.WriteLine(" This will significantly improve VM performance"); + } + System.Console.WriteLine(); + + // Disk Manager + System.Console.WriteLine("3. Disk Manager:"); + try + { + var diskManager = new DiskManager(); + System.Console.WriteLine(" ✅ Disk manager initialized successfully"); + } + catch (Exception ex) + { + System.Console.WriteLine($" ❌ Disk manager initialization failed: {ex.Message}"); + } + System.Console.WriteLine(); + + // VM Configurations + System.Console.WriteLine("4. VM Configurations:"); + try + { + var vmService = new VmManagementService(); + var configs = vmService.GetAllVmConfigurations().ToList(); + System.Console.WriteLine($" Found {configs.Count} VM configuration(s)"); + + foreach (var config in configs) + { + System.Console.WriteLine($" - {config.Name}: {config.Description}"); + foreach (var disk in config.Storage.Disks) + { + var exists = File.Exists(disk.Path); + System.Console.WriteLine($" Disk: {disk.Path} - {(exists ? "✅ Exists" : "❌ Missing")}"); + } + } + } + catch (Exception ex) + { + System.Console.WriteLine($" ❌ Error reading VM configurations: {ex.Message}"); + } + System.Console.WriteLine(); + + // Running VMs + System.Console.WriteLine("5. Running VMs:"); + try + { + var vmService = new VmManagementService(); + var statuses = vmService.GetAllVmStatuses().Where(s => s.State == VmState.Running).ToList(); + System.Console.WriteLine($" Running: {statuses.Count}"); + + foreach (var status in statuses) + { + System.Console.WriteLine($" - {status.Name} (PID: {(status.ProcessId > 0 ? status.ProcessId.ToString() : "N/A")})"); + } + } + catch (Exception ex) + { + System.Console.WriteLine($" ❌ Error checking running VMs: {ex.Message}"); } - return Task.CompletedTask; - } + System.Console.WriteLine(); + System.Console.WriteLine("=== Diagnosis Complete ==="); + } - static async Task ManageDisk(string[] arguments) - { - if (arguments.Length < 1) - { - System.Console.WriteLine("Usage: disk [info|resize|convert]"); - return; - } + static async Task MonitorPerformance(string[] arguments) + { + if (arguments.Length == 0) + { + System.Console.WriteLine("Usage: monitor [start|stop|status]"); + return; + } - var vmName = arguments[0]; - var action = arguments.Length > 1 ? arguments[1].ToLower() : "info"; + var vmName = arguments[0]; + var action = arguments.Length > 1 ? arguments[1].ToLower() : "status"; - switch (action) - { - case "info": - await ShowDiskInfo(vmName); - break; - case "resize": - await ResizeDisk(vmName, arguments.Skip(2).ToArray()); - break; - case "convert": - await ConvertDisk(vmName, arguments.Skip(2).ToArray()); - break; - default: - System.Console.WriteLine($"Unknown disk action: {action}"); - System.Console.WriteLine("Available actions: info, resize, convert"); - break; - } - } + try + { + var vmService = new VmManagementService(); + + switch (action) + { + case "start": + await vmService.StartPerformanceMonitoringAsync(vmName); + System.Console.WriteLine($"Started performance monitoring for VM '{vmName}'"); + break; + + case "stop": + await vmService.StopPerformanceMonitoringAsync(vmName); + System.Console.WriteLine($"Stopped performance monitoring for VM '{vmName}'"); + break; + + case "status": + var isMonitoring = await vmService.IsPerformanceMonitoringActiveAsync(vmName); + System.Console.WriteLine($"Performance monitoring for VM '{vmName}': {(isMonitoring ? "Active" : "Inactive")}"); + break; + + default: + System.Console.WriteLine($"Unknown action: {action}"); + break; + } + } + catch (Exception ex) + { + System.Console.WriteLine($"Error: {ex.Message}"); + } + } - static async Task ShowDiskInfo(string vmName) - { - try - { - var config = _vmService.GetVmConfiguration(vmName); - if (config == null) - { - System.Console.WriteLine($"VM '{vmName}' not found."); - return; - } + static async Task ShowMetrics(string[] arguments) + { + if (arguments.Length == 0) + { + System.Console.WriteLine("Usage: metrics [current|history]"); + return; + } - System.Console.WriteLine($"Disk Information for VM: {vmName}"); - System.Console.WriteLine(new string('-', 50)); + var vmName = arguments[0]; + var type = arguments.Length > 1 ? arguments[1].ToLower() : "current"; - for (int i = 0; i < config.Storage.Disks.Count; i++) - { - var disk = config.Storage.Disks[i]; - System.Console.WriteLine($"Disk {i}:"); - System.Console.WriteLine($" Path: {disk.Path}"); - System.Console.WriteLine($" Format: {disk.Format}"); - System.Console.WriteLine($" Size: {disk.Size} GB"); - System.Console.WriteLine($" Interface: {disk.Interface}"); - System.Console.WriteLine($" Boot: {disk.IsBoot}"); + try + { + var vmService = new VmManagementService(); + + switch (type) + { + case "current": + var currentMetrics = await vmService.GetCurrentPerformanceMetricsAsync(vmName); + if (currentMetrics != null) + { + System.Console.WriteLine($"=== Current Metrics for VM '{vmName}' ==="); + System.Console.WriteLine($"Timestamp: {currentMetrics.Timestamp:yyyy-MM-dd HH:mm:ss}"); + System.Console.WriteLine($"Process ID: {currentMetrics.ProcessId}"); + System.Console.WriteLine($"CPU Usage: {currentMetrics.CpuUsagePercent:F2}%"); + System.Console.WriteLine($"System CPU: {currentMetrics.SystemCpuUsagePercent:F2}%"); + System.Console.WriteLine($"Memory Usage: {currentMetrics.MemoryUsageMB:N0} MB"); + System.Console.WriteLine($"Private Memory: {currentMetrics.PrivateMemoryMB:N0} MB"); + System.Console.WriteLine($"Virtual Memory: {currentMetrics.VirtualMemoryMB:N0} MB"); + System.Console.WriteLine($"Threads: {currentMetrics.ThreadCount}"); + System.Console.WriteLine($"Handles: {currentMetrics.HandleCount}"); + } + else + { + System.Console.WriteLine($"No metrics available for VM '{vmName}'"); + } + break; + + case "history": + var history = await vmService.GetPerformanceHistoryAsync(vmName); + if (history.Any()) + { + System.Console.WriteLine($"=== Performance History for VM '{vmName}' ==="); + System.Console.WriteLine($"{"Timestamp",-20} {"CPU %",-8} {"Memory MB",-12} {"Threads",-8}"); + System.Console.WriteLine(new string('-', 50)); + + foreach (var metric in history.TakeLast(10)) + { + var timestamp = metric.Timestamp.ToString("HH:mm:ss"); + System.Console.WriteLine($"{timestamp,-20} {metric.CpuUsagePercent,-8:F1} {metric.MemoryUsageMB,-12:N0} {metric.ThreadCount,-8}"); + } + } + else + { + System.Console.WriteLine($"No history available for VM '{vmName}'"); + } + break; + + default: + System.Console.WriteLine($"Unknown metrics type: {type}"); + break; + } + } + catch (Exception ex) + { + System.Console.WriteLine($"Error: {ex.Message}"); + } + } - var diskInfo = await _vmService.GetDiskInfoAsync(vmName, i); - if (diskInfo.Exists) - { - System.Console.WriteLine($" Virtual Size: {diskInfo.VirtualSize}"); - System.Console.WriteLine($" Disk Size: {diskInfo.DiskSize}"); - System.Console.WriteLine($" Format: {diskInfo.Format}"); - } - else - { - System.Console.WriteLine(" Status: Not found"); - } - System.Console.WriteLine(); - } - } - catch (Exception ex) - { - System.Console.WriteLine($"Error: {ex.Message}"); - } - } - - static async Task ResizeDisk(string vmName, string[] arguments) - { - if (arguments.Length < 2) - { - System.Console.WriteLine("Usage: disk resize "); - return; - } - - try - { - var diskIndex = int.Parse(arguments[0]); - var newSizeGB = long.Parse(arguments[1]); - - var success = await _vmService.ResizeDiskAsync(vmName, diskIndex, newSizeGB); - if (success) - { - System.Console.WriteLine($"Disk {diskIndex} resized to {newSizeGB} GB successfully."); - } - else - { - System.Console.WriteLine("Failed to resize disk."); - } - } - catch (Exception ex) - { - System.Console.WriteLine($"Error: {ex.Message}"); - } - } - - static async Task ConvertDisk(string vmName, string[] arguments) - { - if (arguments.Length < 2) - { - System.Console.WriteLine("Usage: disk convert "); - System.Console.WriteLine("Available formats: qcow2, raw, vmdk, vdi, vhd"); - return; - } - - try - { - var diskIndex = int.Parse(arguments[0]); - var newFormat = arguments[1]; - - var success = await _vmService.ConvertDiskAsync(vmName, diskIndex, newFormat); - if (success) - { - System.Console.WriteLine($"Disk {diskIndex} converted to {newFormat} format successfully."); - } - else - { - System.Console.WriteLine("Failed to convert disk."); - } - } - catch (Exception ex) - { - System.Console.WriteLine($"Error: {ex.Message}"); - } - } - - static async Task ValidateVm(string[] arguments) - { - if (arguments.Length == 0) - { - System.Console.WriteLine("Usage: validate "); - return; - } - - var vmName = arguments[0]; - - try - { - var isValid = _vmService.ValidateDiskImages(vmName); - if (isValid) - { - System.Console.WriteLine($"VM '{vmName}' disk images are valid."); - } - else - { - System.Console.WriteLine($"VM '{vmName}' has invalid disk images."); - System.Console.WriteLine("Use 'disk info' to check disk status."); - } - } - catch (Exception ex) - { - System.Console.WriteLine($"Error: {ex.Message}"); - } - } - - static async Task DiagnoseSystem() - { - System.Console.WriteLine("=== System Diagnosis ==="); - System.Console.WriteLine(); - - // Check .NET version - System.Console.WriteLine("1. .NET Runtime:"); - System.Console.WriteLine($" Version: {Environment.Version}"); - System.Console.WriteLine($" OS: {Environment.OSVersion}"); - System.Console.WriteLine($" Architecture: {Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")}"); - System.Console.WriteLine(); - - // Check QEMU installation and virtualization - System.Console.WriteLine("2. QEMU Installation:"); - try - { - var processManager = new QemuVmManager.Core.QemuProcessManager(); - var isInstalled = processManager.IsQemuInstalled(); - var version = processManager.GetQemuVersion(); - var accelerators = processManager.GetQemuAccelerators(); - var virtualizationEnabled = processManager.IsVirtualizationEnabled(); - var availableVirtualization = processManager.GetAvailableVirtualization(); - - System.Console.WriteLine($" Installed: {(isInstalled ? "Yes" : "No")}"); - System.Console.WriteLine($" Version: {version}"); - System.Console.WriteLine($" Available Accelerators:"); - foreach (var line in accelerators.Split('\n', StringSplitOptions.RemoveEmptyEntries)) - { - System.Console.WriteLine($" {line.Trim()}"); - } - System.Console.WriteLine($" Virtualization Enabled in BIOS: {(virtualizationEnabled ? "Yes" : "No")}"); - System.Console.WriteLine($" Available Virtualization: {availableVirtualization}"); - - if (!isInstalled) - { - System.Console.WriteLine(" ❌ QEMU is not installed or not found in PATH"); - System.Console.WriteLine(" Please install QEMU and ensure it's available in your system PATH"); - System.Console.WriteLine(" Windows: Download from https://qemu.weilnetz.de/"); - System.Console.WriteLine(" Linux: sudo apt-get install qemu-system-x86_64"); - System.Console.WriteLine(" macOS: brew install qemu"); - } - else if (!virtualizationEnabled) - { - System.Console.WriteLine(" ⚠️ Virtualization is not enabled in BIOS/UEFI"); - System.Console.WriteLine(" Please enable VT-x (Intel) or AMD-V (AMD) in your BIOS settings"); - System.Console.WriteLine(" This will significantly improve VM performance"); - } - else if (availableVirtualization == VirtualizationType.TCG) - { - System.Console.WriteLine(" ⚠️ Only software emulation (TCG) is available"); - System.Console.WriteLine(" Hardware virtualization is not available or not properly configured"); - System.Console.WriteLine(" This may be due to:"); - System.Console.WriteLine(" - QEMU build not supporting hardware acceleration"); - System.Console.WriteLine(" - Missing virtualization drivers"); - System.Console.WriteLine(" - Hyper-V or other virtualization software conflicts"); - } - else - { - System.Console.WriteLine($" ✅ Hardware virtualization ({availableVirtualization}) is available"); - } - } - catch (Exception ex) - { - System.Console.WriteLine($" ❌ Error checking QEMU: {ex.Message}"); - } - System.Console.WriteLine(); - - // Check disk manager - System.Console.WriteLine("3. Disk Manager:"); - try - { - var diskManager = new QemuVmManager.Core.DiskManager(); - System.Console.WriteLine(" ✅ Disk manager initialized successfully"); - } - catch (Exception ex) - { - System.Console.WriteLine($" ❌ Error initializing disk manager: {ex.Message}"); - } - System.Console.WriteLine(); - - // Check VM configurations - System.Console.WriteLine("4. VM Configurations:"); - try - { - var configs = _vmService.GetAllVmConfigurations().ToList(); - System.Console.WriteLine($" Found {configs.Count} VM configuration(s)"); - - foreach (var config in configs) - { - System.Console.WriteLine($" - {config.Name}: {config.Description}"); - - // Check disk images - foreach (var disk in config.Storage.Disks) - { - var exists = File.Exists(disk.Path); - System.Console.WriteLine($" Disk: {disk.Path} - {(exists ? "✅ Exists" : "❌ Missing")}"); - } - } - } - catch (Exception ex) - { - System.Console.WriteLine($" ❌ Error checking VM configurations: {ex.Message}"); - } - System.Console.WriteLine(); - - // Check running VMs - System.Console.WriteLine("5. Running VMs:"); - try - { - var statuses = _vmService.GetAllVmStatuses().ToList(); - var runningVms = statuses.Where(s => s.State == QemuVmManager.Models.VmState.Running).ToList(); - - System.Console.WriteLine($" Running: {runningVms.Count}"); - foreach (var vm in runningVms) - { - System.Console.WriteLine($" - {vm.Name} (PID: {vm.ProcessId})"); - } - } - catch (Exception ex) - { - System.Console.WriteLine($" ❌ Error checking running VMs: {ex.Message}"); - } - System.Console.WriteLine(); - - System.Console.WriteLine("=== Diagnosis Complete ==="); - } - - static string GetUserInput(string prompt, string defaultValue = "") - { - System.Console.Write(prompt); - var input = System.Console.ReadLine()?.Trim(); - return string.IsNullOrEmpty(input) ? defaultValue : input; - } - - static async Task MonitorPerformance(string[] arguments) - { - if (arguments.Length == 0) - { - System.Console.WriteLine("Usage: monitor [start|stop|status]"); - return; - } - - var vmName = arguments[0]; - var action = arguments.Length > 1 ? arguments[1].ToLower() : "status"; - - try - { - switch (action) - { - case "start": - await _vmService.StartPerformanceMonitoringAsync(vmName); - System.Console.WriteLine($"Performance monitoring started for VM '{vmName}'"); - break; - case "stop": - _vmService.StopPerformanceMonitoring(vmName); - System.Console.WriteLine($"Performance monitoring stopped for VM '{vmName}'"); - break; - case "status": - default: - var isMonitoring = _vmService.IsPerformanceMonitoringActive(vmName); - System.Console.WriteLine($"Performance monitoring for VM '{vmName}': {(isMonitoring ? "Active" : "Inactive")}"); - break; - } - } - catch (Exception ex) - { - System.Console.WriteLine($"Error: {ex.Message}"); - } - } - - static async Task ShowMetrics(string[] arguments) - { - if (arguments.Length == 0) - { - System.Console.WriteLine("Usage: metrics [current|history]"); - return; - } - - var vmName = arguments[0]; - var type = arguments.Length > 1 ? arguments[1].ToLower() : "current"; - - try - { - switch (type) - { - case "current": - var currentMetrics = await _vmService.GetVmPerformanceMetricsAsync(vmName); - DisplayPerformanceMetrics(currentMetrics, "Current"); - break; - case "history": - var history = await _vmService.GetPerformanceHistoryAsync(vmName, 20); - System.Console.WriteLine($"Performance History for VM '{vmName}' (Last {history.Count} samples):"); - System.Console.WriteLine(); - - foreach (var metrics in history.TakeLast(10)) - { - DisplayPerformanceMetrics(metrics, metrics.Timestamp.ToString("HH:mm:ss")); - } - break; - default: - System.Console.WriteLine("Invalid metrics type. Use 'current' or 'history'"); - break; - } - } - catch (Exception ex) - { - System.Console.WriteLine($"Error: {ex.Message}"); - } - } - - static void DisplayPerformanceMetrics(VmPerformanceMetrics metrics, string label) - { - System.Console.WriteLine($"=== {label} Performance Metrics ==="); - System.Console.WriteLine($"Timestamp: {metrics.Timestamp:yyyy-MM-dd HH:mm:ss}"); - System.Console.WriteLine($"Process ID: {metrics.ProcessId}"); - System.Console.WriteLine(); - System.Console.WriteLine("CPU Usage:"); - System.Console.WriteLine($" VM CPU: {metrics.CpuUsagePercent:F2}%"); - System.Console.WriteLine($" System CPU: {metrics.SystemCpuUsagePercent:F2}%"); - System.Console.WriteLine(); - System.Console.WriteLine("Memory Usage:"); - System.Console.WriteLine($" Working Set: {metrics.MemoryUsageMB:N0} MB"); - System.Console.WriteLine($" Private Memory: {metrics.PrivateMemoryMB:N0} MB"); - System.Console.WriteLine($" Virtual Memory: {metrics.VirtualMemoryMB:N0} MB"); - System.Console.WriteLine(); - System.Console.WriteLine("Process Info:"); - System.Console.WriteLine($" Threads: {metrics.ThreadCount}"); - System.Console.WriteLine($" Handles: {metrics.HandleCount}"); - System.Console.WriteLine(); - } - } + static string GetUserInput(string prompt, string defaultValue = "") + { + System.Console.Write(prompt); + var input = System.Console.ReadLine()?.Trim(); + return string.IsNullOrEmpty(input) ? defaultValue : input; + } +} diff --git a/QemuVmManager.Core/P2PNode.cs b/QemuVmManager.Core/P2PNode.cs new file mode 100644 index 0000000..b15b9eb --- /dev/null +++ b/QemuVmManager.Core/P2PNode.cs @@ -0,0 +1,834 @@ +using System.Net; +using System.Net.Sockets; +using System.Text.Json; +using Microsoft.Extensions.Logging; +using QemuVmManager.Models; + +namespace QemuVmManager.Core; + +public class P2PNode : IDisposable +{ + private readonly string _nodeId; + private readonly int _port; + private readonly IUPnPManager _upnpManager; + private readonly QemuProcessManager _qemuManager; + private readonly ILogger? _logger; + + private readonly Dictionary _knownNodes = new(); + private readonly Dictionary _localVms = new(); + private readonly object _clusterLock = new(); + + private UdpClient? _udpClient; + private TcpListener? _tcpListener; + private CancellationTokenSource? _cancellationTokenSource; + private Task? _heartbeatTask; + private Task? _electionTask; + private Task? _discoveryTask; + + private NodeRole _currentRole = NodeRole.Follower; + private long _currentTerm = 0; + private string? _votedFor; + private DateTime _lastHeartbeat = DateTime.UtcNow; + private bool _isRunning = false; + + public event EventHandler? RoleChanged; + public event EventHandler? NodeJoined; + public event EventHandler? NodeLeft; + public event EventHandler? VmStarted; + public event EventHandler? VmStopped; + + public NodeInfo CurrentNode { get; private set; } + public ClusterState ClusterState { get; private set; } = new(); + public bool IsMaster => _currentRole == NodeRole.Master; + + public P2PNode(string nodeId, int port = 8080, IUPnPManager? upnpManager = null, + QemuProcessManager? qemuManager = null, ILogger? logger = null) + { + _nodeId = nodeId; + _port = port; + _upnpManager = upnpManager ?? new UPnPManager(); + _qemuManager = qemuManager ?? new QemuProcessManager(); + _logger = logger; + + CurrentNode = new NodeInfo + { + NodeId = _nodeId, + Hostname = Environment.MachineName, + IpAddress = GetLocalIpAddress(), + Port = _port, + Role = NodeRole.Follower, + State = NodeState.Stopped, + SystemInfo = GetSystemInfo() + }; + } + + public async Task StartAsync() + { + if (_isRunning) + return; + + _logger?.LogInformation("Starting P2P node {NodeId} on port {Port}", _nodeId, _port); + + _cancellationTokenSource = new CancellationTokenSource(); + _isRunning = true; + CurrentNode.State = NodeState.Starting; + + try + { + // Initialize network components + await InitializeNetworkAsync(); + + // Start background tasks + _heartbeatTask = Task.Run(() => HeartbeatLoopAsync(_cancellationTokenSource.Token)); + _electionTask = Task.Run(() => ElectionLoopAsync(_cancellationTokenSource.Token)); + _discoveryTask = Task.Run(() => DiscoveryLoopAsync(_cancellationTokenSource.Token)); + + CurrentNode.State = NodeState.Running; + _logger?.LogInformation("P2P node {NodeId} started successfully", _nodeId); + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to start P2P node {NodeId}", _nodeId); + CurrentNode.State = NodeState.Error; + throw; + } + } + + public async Task StopAsync() + { + if (!_isRunning) + return; + + _logger?.LogInformation("Stopping P2P node {NodeId}", _nodeId); + + _isRunning = false; + CurrentNode.State = NodeState.Stopping; + + _cancellationTokenSource?.Cancel(); + + // Stop all local VMs + foreach (var vm in _localVms.Values.ToList()) + { + await StopLocalVmAsync(vm.VmId); + } + + // Clean up network resources + _udpClient?.Close(); + _tcpListener?.Stop(); + + CurrentNode.State = NodeState.Stopped; + _logger?.LogInformation("P2P node {NodeId} stopped", _nodeId); + } + + public async Task StartVmAsync(VmConfiguration config, string? targetNodeId = null) + { + var vmId = Guid.NewGuid().ToString(); + var targetNode = targetNodeId ?? _nodeId; + + if (targetNode == _nodeId) + { + // Start VM locally + return await StartLocalVmAsync(vmId, config); + } + else + { + // Request remote node to start VM + return await RequestRemoteVmStartAsync(vmId, config, targetNode); + } + } + + public async Task StopVmAsync(string vmId) + { + if (_localVms.ContainsKey(vmId)) + { + return await StopLocalVmAsync(vmId); + } + else + { + // Find which node has this VM and request stop + var vm = ClusterState.DistributedVms.GetValueOrDefault(vmId); + if (vm != null) + { + return await RequestRemoteVmStopAsync(vmId, vm.NodeId); + } + } + + return false; + } + + public async Task RequestPortForwardingAsync(string vmId, int privatePort, int? publicPort = null) + { + if (!IsMaster) + { + throw new InvalidOperationException("Only the master node can request port forwarding"); + } + + var vm = ClusterState.DistributedVms.GetValueOrDefault(vmId); + if (vm == null) + { + return new PortForwardingResponse + { + VmId = vmId, + Success = false, + ErrorMessage = "VM not found" + }; + } + + try + { + var actualPublicPort = publicPort ?? await GetAvailablePortAsync(); + var success = await _upnpManager.AddPortMappingAsync(actualPublicPort, privatePort, $"QEMU VM {vmId}"); + + if (success) + { + var externalIp = await _upnpManager.GetExternalIpAddressAsync(); + vm.PublicEndpoint = new NetworkEndpoint + { + PublicIp = externalIp ?? IPAddress.Any, + PublicPort = actualPublicPort, + PrivateIp = vm.Configuration.Network.Interfaces.FirstOrDefault()?.Mac != null ? + IPAddress.Parse("192.168.1.100") : IPAddress.Any, // Simplified + PrivatePort = privatePort, + Protocol = "TCP", + Description = $"QEMU VM {vmId}" + }; + + return new PortForwardingResponse + { + VmId = vmId, + Success = true, + PublicIp = externalIp, + PublicPort = actualPublicPort + }; + } + + return new PortForwardingResponse + { + VmId = vmId, + Success = false, + ErrorMessage = "Failed to create port mapping" + }; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to request port forwarding for VM {VmId}", vmId); + return new PortForwardingResponse + { + VmId = vmId, + Success = false, + ErrorMessage = ex.Message + }; + } + } + + public async Task MigrateVmAsync(string vmId, string targetNodeId) + { + var vm = ClusterState.DistributedVms.GetValueOrDefault(vmId); + if (vm == null) + { + return new VmMigrationResponse + { + VmId = vmId, + Success = false, + ErrorMessage = "VM not found" + }; + } + + if (vm.NodeId == targetNodeId) + { + return new VmMigrationResponse + { + VmId = vmId, + Success = true, + ErrorMessage = "VM is already on target node" + }; + } + + try + { + // Stop VM on source node + await StopVmAsync(vmId); + + // Start VM on target node + var newVm = await StartVmAsync(vm.Configuration, targetNodeId); + + return new VmMigrationResponse + { + VmId = vmId, + Success = true + }; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to migrate VM {VmId} to node {TargetNodeId}", vmId, targetNodeId); + return new VmMigrationResponse + { + VmId = vmId, + Success = false, + ErrorMessage = ex.Message + }; + } + } + + private async Task InitializeNetworkAsync() + { + // Initialize UDP client for discovery and heartbeats + _udpClient = new UdpClient(_port); + _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + + // Initialize TCP listener for direct communication + _tcpListener = new TcpListener(IPAddress.Any, _port); + _tcpListener.Start(); + + // Start listening for incoming connections + _ = Task.Run(() => ListenForConnectionsAsync(_cancellationTokenSource!.Token)); + } + + private async Task HeartbeatLoopAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + var heartbeat = new HeartbeatMessage + { + NodeId = _nodeId, + Role = _currentRole, + Term = _currentTerm, + Timestamp = DateTime.UtcNow, + Metadata = new Dictionary + { + ["running_vms"] = _localVms.Count, + ["cpu_usage"] = await GetCpuUsageAsync(), + ["memory_usage"] = await GetMemoryUsageAsync() + } + }; + + await BroadcastHeartbeatAsync(heartbeat); + + // Check for stale nodes + await CleanupStaleNodesAsync(); + + await Task.Delay(5000, cancellationToken); // Send heartbeat every 5 seconds + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error in heartbeat loop"); + await Task.Delay(1000, cancellationToken); + } + } + } + + private async Task ElectionLoopAsync(CancellationToken cancellationToken) + { + var electionTimeout = TimeSpan.FromSeconds(10 + Random.Shared.Next(10)); // 10-20 seconds + + while (!cancellationToken.IsCancellationRequested) + { + try + { + if (_currentRole == NodeRole.Follower) + { + // Wait for heartbeat from master + var timeout = DateTime.UtcNow.Add(electionTimeout); + while (DateTime.UtcNow < timeout && !cancellationToken.IsCancellationRequested) + { + if (_lastHeartbeat > DateTime.UtcNow.AddSeconds(-5)) + { + await Task.Delay(1000, cancellationToken); + continue; + } + break; + } + + if (!cancellationToken.IsCancellationRequested) + { + // No heartbeat received, start election + await StartElectionAsync(); + } + } + else if (_currentRole == NodeRole.Candidate) + { + // Wait for election results + await Task.Delay(5000, cancellationToken); + } + else if (_currentRole == NodeRole.Master) + { + // Master continues to send heartbeats + await Task.Delay(1000, cancellationToken); + } + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error in election loop"); + await Task.Delay(1000, cancellationToken); + } + } + } + + private async Task DiscoveryLoopAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + // Send discovery message + var discoveryMessage = JsonSerializer.Serialize(new + { + type = "discovery", + nodeId = _nodeId, + timestamp = DateTime.UtcNow + }); + + var data = System.Text.Encoding.UTF8.GetBytes(discoveryMessage); + await _udpClient!.SendAsync(data, data.Length, new IPEndPoint(IPAddress.Broadcast, _port)); + + await Task.Delay(30000, cancellationToken); // Send discovery every 30 seconds + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error in discovery loop"); + await Task.Delay(5000, cancellationToken); + } + } + } + + private async Task StartElectionAsync() + { + _logger?.LogInformation("Starting election for term {Term}", _currentTerm + 1); + + _currentTerm++; + _currentRole = NodeRole.Candidate; + _votedFor = _nodeId; + + var electionRequest = new ElectionRequest + { + CandidateId = _nodeId, + Term = _currentTerm, + Timestamp = DateTime.UtcNow + }; + + var votes = 1; // Vote for self + var requiredVotes = (_knownNodes.Count + 1) / 2 + 1; // Majority + + // Request votes from other nodes + foreach (var node in _knownNodes.Values) + { + try + { + var response = await RequestVoteAsync(node, electionRequest); + if (response.VoteGranted && response.Term == _currentTerm) + { + votes++; + } + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to request vote from node {NodeId}", node.NodeId); + } + } + + if (votes >= requiredVotes) + { + await BecomeMasterAsync(); + } + else + { + _currentRole = NodeRole.Follower; + } + } + + private async Task BecomeMasterAsync() + { + _logger?.LogInformation("Becoming master for term {Term}", _currentTerm); + + _currentRole = NodeRole.Master; + CurrentNode.Role = NodeRole.Master; + + // Update cluster state + lock (_clusterLock) + { + ClusterState.MasterNode = CurrentNode; + ClusterState.LastUpdated = DateTime.UtcNow; + } + + RoleChanged?.Invoke(this, NodeRole.Master); + + // Start master-specific tasks + if (IsMaster) + { + await StartMasterTasksAsync(); + } + } + + private async Task StartMasterTasksAsync() + { + // Master-specific initialization + _logger?.LogInformation("Starting master tasks"); + + // Check UPnP availability + var upnpAvailable = await _upnpManager.IsUPnPAvailableAsync(); + _logger?.LogInformation("UPnP available: {Available}", upnpAvailable); + } + + private async Task StartLocalVmAsync(string vmId, VmConfiguration config) + { + try + { + var success = await _qemuManager.StartVmAsync(config); + if (!success) + { + throw new InvalidOperationException("Failed to start QEMU VM"); + } + + var vmInstance = new VmInstance + { + VmId = vmId, + VmName = config.Name, + NodeId = _nodeId, + State = VmState.Running, + StartedAt = DateTime.UtcNow, + Configuration = config + }; + + _localVms[vmId] = vmInstance; + + lock (_clusterLock) + { + ClusterState.DistributedVms[vmId] = vmInstance; + ClusterState.LastUpdated = DateTime.UtcNow; + } + + VmStarted?.Invoke(this, vmInstance); + _logger?.LogInformation("Started local VM {VmId} ({VmName})", vmId, config.Name); + + return vmInstance; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to start local VM {VmId}", vmId); + throw; + } + } + + private async Task StopLocalVmAsync(string vmId) + { + if (!_localVms.TryGetValue(vmId, out var vm)) + { + return false; + } + + try + { + var success = await _qemuManager.StopVmAsync(vm.VmName, true); + if (success) + { + _localVms.Remove(vmId); + + lock (_clusterLock) + { + ClusterState.DistributedVms.Remove(vmId); + ClusterState.LastUpdated = DateTime.UtcNow; + } + + VmStopped?.Invoke(this, vm); + _logger?.LogInformation("Stopped local VM {VmId}", vmId); + } + + return success; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to stop local VM {VmId}", vmId); + return false; + } + } + + private async Task RequestRemoteVmStartAsync(string vmId, VmConfiguration config, string targetNodeId) + { + // This would implement RPC to remote node + throw new NotImplementedException("Remote VM start not yet implemented"); + } + + private async Task RequestRemoteVmStopAsync(string vmId, string targetNodeId) + { + // This would implement RPC to remote node + throw new NotImplementedException("Remote VM stop not yet implemented"); + } + + private async Task RequestVoteAsync(NodeInfo node, ElectionRequest request) + { + // This would implement RPC to request vote + throw new NotImplementedException("Vote request not yet implemented"); + } + + private async Task BroadcastHeartbeatAsync(HeartbeatMessage heartbeat) + { + var message = JsonSerializer.Serialize(heartbeat); + var data = System.Text.Encoding.UTF8.GetBytes(message); + + foreach (var node in _knownNodes.Values) + { + try + { + await _udpClient!.SendAsync(data, data.Length, new IPEndPoint(node.IpAddress, node.Port)); + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to send heartbeat to node {NodeId}", node.NodeId); + } + } + } + + private async Task CleanupStaleNodesAsync() + { + var staleThreshold = DateTime.UtcNow.AddSeconds(-30); + var staleNodes = _knownNodes.Values.Where(n => n.LastSeen < staleThreshold).ToList(); + + foreach (var node in staleNodes) + { + _knownNodes.Remove(node.NodeId); + NodeLeft?.Invoke(this, node); + _logger?.LogInformation("Removed stale node {NodeId}", node.NodeId); + } + } + + private async Task ListenForConnectionsAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + var client = await _tcpListener!.AcceptTcpClientAsync(cancellationToken); + _ = Task.Run(() => HandleClientAsync(client, cancellationToken)); + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error accepting client connection"); + } + } + } + + private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken) + { + try + { + using var stream = client.GetStream(); + using var reader = new StreamReader(stream); + using var writer = new StreamWriter(stream); + + var message = await reader.ReadLineAsync(); + if (message != null) + { + await ProcessMessageAsync(message, writer); + } + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error handling client connection"); + } + finally + { + client.Close(); + } + } + + private async Task ProcessMessageAsync(string message, StreamWriter writer) + { + try + { + var data = JsonSerializer.Deserialize(message); + var messageType = data.GetProperty("type").GetString(); + + switch (messageType) + { + case "heartbeat": + await ProcessHeartbeatAsync(data); + break; + case "discovery": + await ProcessDiscoveryAsync(data); + break; + case "election_request": + await ProcessElectionRequestAsync(data, writer); + break; + case "election_response": + await ProcessElectionResponseAsync(data); + break; + default: + _logger?.LogWarning("Unknown message type: {MessageType}", messageType); + break; + } + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error processing message: {Message}", message); + } + } + + private async Task ProcessHeartbeatAsync(JsonElement data) + { + var nodeId = data.GetProperty("nodeId").GetString()!; + var role = Enum.Parse(data.GetProperty("role").GetString()!); + var term = data.GetProperty("term").GetInt64(); + var timestamp = data.GetProperty("timestamp").GetDateTime(); + + if (term > _currentTerm) + { + _currentTerm = term; + _currentRole = NodeRole.Follower; + _votedFor = null; + } + + if (role == NodeRole.Master && term >= _currentTerm) + { + _currentRole = NodeRole.Follower; + _lastHeartbeat = timestamp; + } + + // Update node info + if (_knownNodes.TryGetValue(nodeId, out var node)) + { + node.LastSeen = DateTime.UtcNow; + node.Role = role; + } + } + + private async Task ProcessDiscoveryAsync(JsonElement data) + { + var nodeId = data.GetProperty("nodeId").GetString()!; + + if (nodeId != _nodeId && !_knownNodes.ContainsKey(nodeId)) + { + var newNode = new NodeInfo + { + NodeId = nodeId, + LastSeen = DateTime.UtcNow + }; + + _knownNodes[nodeId] = newNode; + NodeJoined?.Invoke(this, newNode); + _logger?.LogInformation("Discovered new node {NodeId}", nodeId); + } + } + + private async Task ProcessElectionRequestAsync(JsonElement data, StreamWriter writer) + { + var candidateId = data.GetProperty("candidateId").GetString()!; + var term = data.GetProperty("term").GetInt64(); + + var response = new ElectionResponse + { + VoterId = _nodeId, + Term = _currentTerm, + VoteGranted = false + }; + + if (term > _currentTerm) + { + _currentTerm = term; + _currentRole = NodeRole.Follower; + _votedFor = null; + } + + if (term == _currentTerm && (_votedFor == null || _votedFor == candidateId)) + { + _votedFor = candidateId; + response.VoteGranted = true; + } + + var responseJson = JsonSerializer.Serialize(response); + await writer.WriteLineAsync(responseJson); + await writer.FlushAsync(); + } + + private async Task ProcessElectionResponseAsync(JsonElement data) + { + // Handle election response + var voterId = data.GetProperty("voterId").GetString()!; + var voteGranted = data.GetProperty("voteGranted").GetBoolean(); + var term = data.GetProperty("term").GetInt64(); + + if (voteGranted && term == _currentTerm && _currentRole == NodeRole.Candidate) + { + // Count votes and potentially become master + // This is simplified - in a real implementation you'd track votes + } + } + + private async Task GetAvailablePortAsync() + { + // Find an available port for UPnP mapping + using var listener = new TcpListener(IPAddress.Any, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } + + private async Task GetCpuUsageAsync() + { + // Simplified CPU usage calculation + return Environment.ProcessorCount * 0.1; // 10% per core + } + + private async Task GetMemoryUsageAsync() + { + // Simplified memory usage calculation + return GC.GetTotalMemory(false) / (1024.0 * 1024.0); // MB + } + + private IPAddress GetLocalIpAddress() + { + try + { + var host = Dns.GetHostEntry(Dns.GetHostName()); + return host.AddressList.FirstOrDefault(ip => + ip.AddressFamily == AddressFamily.InterNetwork && + !IPAddress.IsLoopback(ip)) ?? IPAddress.Any; + } + catch + { + return IPAddress.Any; + } + } + + private SystemInfo GetSystemInfo() + { + return new SystemInfo + { + OsName = Environment.OSVersion.Platform.ToString(), + OsVersion = Environment.OSVersion.VersionString, + Architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") ?? "Unknown", + CpuCores = Environment.ProcessorCount, + TotalMemory = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes / (1024 * 1024), // MB + AvailableMemory = GC.GetTotalMemory(false) / (1024 * 1024), // MB + AvailableVirtualization = _qemuManager.GetAvailableVirtualization(), + QemuInstalled = _qemuManager.IsQemuInstalled(), + QemuVersion = _qemuManager.GetQemuVersion() + }; + } + + public void Dispose() + { + StopAsync().Wait(); + _udpClient?.Dispose(); + _tcpListener?.Stop(); + _cancellationTokenSource?.Dispose(); + } +} diff --git a/QemuVmManager.Core/QemuVmManager.Core.csproj b/QemuVmManager.Core/QemuVmManager.Core.csproj index cc9f32f..fc954a4 100644 --- a/QemuVmManager.Core/QemuVmManager.Core.csproj +++ b/QemuVmManager.Core/QemuVmManager.Core.csproj @@ -12,6 +12,7 @@ + diff --git a/QemuVmManager.Core/UPnPManager.cs b/QemuVmManager.Core/UPnPManager.cs new file mode 100644 index 0000000..d167657 --- /dev/null +++ b/QemuVmManager.Core/UPnPManager.cs @@ -0,0 +1,377 @@ +using System.Net; +using System.Net.Sockets; +using Microsoft.Extensions.Logging; +using QemuVmManager.Models; + +namespace QemuVmManager.Core; + +public interface IUPnPManager +{ + Task IsUPnPAvailableAsync(); + Task GetExternalIpAddressAsync(); + Task AddPortMappingAsync(int externalPort, int internalPort, string description); + Task RemovePortMappingAsync(int externalPort); + Task> GetPortMappingsAsync(); +} + +public class UPnPManager : IUPnPManager +{ + private readonly ILogger? _logger; + private readonly Dictionary _activeMappings = new(); + private readonly object _lock = new(); + + public UPnPManager(ILogger? logger = null) + { + _logger = logger; + } + + public async Task IsUPnPAvailableAsync() + { + try + { + // Try to discover UPnP devices + var devices = await DiscoverUPnPDevicesAsync(); + return devices.Any(); + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "UPnP discovery failed"); + return false; + } + } + + public async Task GetExternalIpAddressAsync() + { + try + { + // Try multiple methods to get external IP + var externalIp = await GetExternalIpFromUPnPAsync(); + if (externalIp != null) + return externalIp; + + // Fallback to external service + return await GetExternalIpFromServiceAsync(); + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to get external IP address"); + return null; + } + } + + public async Task AddPortMappingAsync(int externalPort, int internalPort, string description) + { + try + { + var localIp = GetLocalIpAddress(); + if (localIp == null) + { + _logger?.LogError("Could not determine local IP address"); + return false; + } + + var success = await AddUPnPPortMappingAsync(externalPort, internalPort, localIp, description); + if (success) + { + lock (_lock) + { + _activeMappings[externalPort] = new PortMapping + { + ExternalPort = externalPort, + InternalPort = internalPort, + InternalIp = localIp, + Protocol = "TCP", + Description = description, + CreatedAt = DateTime.UtcNow + }; + } + _logger?.LogInformation("Added port mapping: {ExternalPort} -> {InternalPort} ({Description})", + externalPort, internalPort, description); + } + + return success; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to add port mapping {ExternalPort} -> {InternalPort}", + externalPort, internalPort); + return false; + } + } + + public async Task RemovePortMappingAsync(int externalPort) + { + try + { + var success = await RemoveUPnPPortMappingAsync(externalPort); + if (success) + { + lock (_lock) + { + _activeMappings.Remove(externalPort); + } + _logger?.LogInformation("Removed port mapping: {ExternalPort}", externalPort); + } + + return success; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to remove port mapping {ExternalPort}", externalPort); + return false; + } + } + + public async Task> GetPortMappingsAsync() + { + try + { + var mappings = await GetUPnPPortMappingsAsync(); + lock (_lock) + { + // Merge with our active mappings + foreach (var mapping in _activeMappings.Values) + { + if (!mappings.Any(m => m.ExternalPort == mapping.ExternalPort)) + { + mappings.Add(mapping); + } + } + } + return mappings; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to get port mappings"); + lock (_lock) + { + return _activeMappings.Values.ToList(); + } + } + } + + private async Task> DiscoverUPnPDevicesAsync() + { + var devices = new List(); + + // Simple SSDP discovery + var ssdpMessage = + "M-SEARCH * HTTP/1.1\r\n" + + "HOST: 239.255.255.250:1900\r\n" + + "MAN: \"ssdp:discover\"\r\n" + + "MX: 3\r\n" + + "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" + + "\r\n"; + + using var udpClient = new UdpClient(); + udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + + var endpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900); + var data = System.Text.Encoding.UTF8.GetBytes(ssdpMessage); + + await udpClient.SendAsync(data, data.Length, endpoint); + + // Wait for responses + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + try + { + while (!cts.Token.IsCancellationRequested) + { + var result = await udpClient.ReceiveAsync(cts.Token); + var response = System.Text.Encoding.UTF8.GetString(result.Buffer); + + if (response.Contains("InternetGatewayDevice")) + { + var device = ParseUPnPResponse(response, result.RemoteEndPoint); + if (device != null) + devices.Add(device); + } + } + } + catch (OperationCanceledException) + { + // Timeout - this is expected + } + + return devices; + } + + private UPnPDevice? ParseUPnPResponse(string response, IPEndPoint endpoint) + { + try + { + var lines = response.Split('\n'); + var location = lines.FirstOrDefault(l => l.StartsWith("LOCATION:", StringComparison.OrdinalIgnoreCase)); + + if (location != null) + { + var url = location.Substring("LOCATION:".Length).Trim(); + return new UPnPDevice + { + ControlUrl = url, + IpAddress = endpoint.Address, + Port = endpoint.Port + }; + } + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to parse UPnP response"); + } + + return null; + } + + private async Task GetExternalIpFromUPnPAsync() + { + try + { + var devices = await DiscoverUPnPDevicesAsync(); + if (!devices.Any()) + return null; + + // Use the first device to get external IP + var device = devices.First(); + // This would require implementing SOAP calls to the UPnP device + // For now, we'll use the fallback method + return null; + } + catch + { + return null; + } + } + + private async Task GetExternalIpFromServiceAsync() + { + try + { + using var client = new HttpClient(); + client.Timeout = TimeSpan.FromSeconds(10); + + // Try multiple external IP services + var services = new[] + { + "https://api.ipify.org", + "https://icanhazip.com", + "https://ifconfig.me/ip" + }; + + foreach (var service in services) + { + try + { + var response = await client.GetStringAsync(service); + var ipString = response.Trim(); + if (IPAddress.TryParse(ipString, out var ip)) + return ip; + } + catch + { + continue; + } + } + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to get external IP from services"); + } + + return null; + } + + private IPAddress? GetLocalIpAddress() + { + try + { + var host = Dns.GetHostEntry(Dns.GetHostName()); + return host.AddressList.FirstOrDefault(ip => + ip.AddressFamily == AddressFamily.InterNetwork && + !IPAddress.IsLoopback(ip)); + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to get local IP address"); + return null; + } + } + + private async Task AddUPnPPortMappingAsync(int externalPort, int internalPort, IPAddress internalIp, string description) + { + try + { + var devices = await DiscoverUPnPDevicesAsync(); + if (!devices.Any()) + return false; + + // This would require implementing SOAP calls to add port mapping + // For now, we'll simulate success + _logger?.LogInformation("Simulating UPnP port mapping: {ExternalPort} -> {InternalIp}:{InternalPort}", + externalPort, internalIp, internalPort); + + return true; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to add UPnP port mapping"); + return false; + } + } + + private async Task RemoveUPnPPortMappingAsync(int externalPort) + { + try + { + var devices = await DiscoverUPnPDevicesAsync(); + if (!devices.Any()) + return false; + + // This would require implementing SOAP calls to remove port mapping + // For now, we'll simulate success + _logger?.LogInformation("Simulating UPnP port mapping removal: {ExternalPort}", externalPort); + + return true; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to remove UPnP port mapping"); + return false; + } + } + + private async Task> GetUPnPPortMappingsAsync() + { + try + { + var devices = await DiscoverUPnPDevicesAsync(); + if (!devices.Any()) + return new List(); + + // This would require implementing SOAP calls to get port mappings + // For now, we'll return empty list + return new List(); + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to get UPnP port mappings"); + return new List(); + } + } +} + +public class UPnPDevice +{ + public string ControlUrl { get; set; } = string.Empty; + public IPAddress IpAddress { get; set; } = IPAddress.Any; + public int Port { get; set; } +} + +public class PortMapping +{ + public int ExternalPort { get; set; } + public int InternalPort { get; set; } + public IPAddress InternalIp { get; set; } = IPAddress.Any; + public string Protocol { get; set; } = "TCP"; + public string Description { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } +} diff --git a/QemuVmManager.Models/P2PModels.cs b/QemuVmManager.Models/P2PModels.cs new file mode 100644 index 0000000..a12da77 --- /dev/null +++ b/QemuVmManager.Models/P2PModels.cs @@ -0,0 +1,137 @@ +using System.Net; + +namespace QemuVmManager.Models; + +public enum NodeRole +{ + Follower, + Candidate, + Master +} + +public enum NodeState +{ + Starting, + Running, + Stopping, + Stopped, + Error +} + +public class NodeInfo +{ + public string NodeId { get; set; } = string.Empty; + public string Hostname { get; set; } = string.Empty; + public IPAddress IpAddress { get; set; } = IPAddress.Any; + public int Port { get; set; } = 8080; + public NodeRole Role { get; set; } = NodeRole.Follower; + public NodeState State { get; set; } = NodeState.Stopped; + public DateTime LastSeen { get; set; } = DateTime.UtcNow; + public Dictionary Metadata { get; set; } = new(); + public List RunningVms { get; set; } = new(); + public SystemInfo SystemInfo { get; set; } = new(); +} + +public class SystemInfo +{ + public string OsName { get; set; } = string.Empty; + public string OsVersion { get; set; } = string.Empty; + public string Architecture { get; set; } = string.Empty; + public int CpuCores { get; set; } + public long TotalMemory { get; set; } + public long AvailableMemory { get; set; } + public VirtualizationType AvailableVirtualization { get; set; } + public bool QemuInstalled { get; set; } + public string QemuVersion { get; set; } = string.Empty; +} + +public class ClusterState +{ + public string ClusterId { get; set; } = string.Empty; + public NodeInfo? MasterNode { get; set; } + public List Nodes { get; set; } = new(); + public DateTime LastUpdated { get; set; } = DateTime.UtcNow; + public Dictionary DistributedVms { get; set; } = new(); +} + +public class VmInstance +{ + public string VmId { get; set; } = string.Empty; + public string VmName { get; set; } = string.Empty; + public string NodeId { get; set; } = string.Empty; + public VmState State { get; set; } = VmState.Stopped; + public DateTime StartedAt { get; set; } + public VmConfiguration Configuration { get; set; } = new(); + public NetworkEndpoint? PublicEndpoint { get; set; } + public VmPerformanceMetrics? PerformanceMetrics { get; set; } +} + +public class NetworkEndpoint +{ + public IPAddress PublicIp { get; set; } = IPAddress.Any; + public int PublicPort { get; set; } + public IPAddress PrivateIp { get; set; } = IPAddress.Any; + public int PrivatePort { get; set; } + public string Protocol { get; set; } = "TCP"; + public string Description { get; set; } = string.Empty; +} + +public class ElectionRequest +{ + public string CandidateId { get; set; } = string.Empty; + public long Term { get; set; } + public DateTime Timestamp { get; set; } = DateTime.UtcNow; +} + +public class ElectionResponse +{ + public string VoterId { get; set; } = string.Empty; + public bool VoteGranted { get; set; } + public long Term { get; set; } + public DateTime Timestamp { get; set; } = DateTime.UtcNow; +} + +public class HeartbeatMessage +{ + public string NodeId { get; set; } = string.Empty; + public NodeRole Role { get; set; } + public long Term { get; set; } + public DateTime Timestamp { get; set; } = DateTime.UtcNow; + public Dictionary Metadata { get; set; } = new(); +} + +public class VmMigrationRequest +{ + public string VmId { get; set; } = string.Empty; + public string SourceNodeId { get; set; } = string.Empty; + public string TargetNodeId { get; set; } = string.Empty; + public bool LiveMigration { get; set; } = false; + public DateTime RequestedAt { get; set; } = DateTime.UtcNow; +} + +public class VmMigrationResponse +{ + public string VmId { get; set; } = string.Empty; + public bool Success { get; set; } + public string? ErrorMessage { get; set; } + public DateTime CompletedAt { get; set; } = DateTime.UtcNow; +} + +public class PortForwardingRequest +{ + public string VmId { get; set; } = string.Empty; + public int PrivatePort { get; set; } + public int? PublicPort { get; set; } + public string Protocol { get; set; } = "TCP"; + public string Description { get; set; } = string.Empty; +} + +public class PortForwardingResponse +{ + public string VmId { get; set; } = string.Empty; + public bool Success { get; set; } + public IPAddress? PublicIp { get; set; } + public int? PublicPort { get; set; } + public string? ErrorMessage { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/QemuVmManager.Services/VmManagementService.cs b/QemuVmManager.Services/VmManagementService.cs index 5d0c137..de0dc1f 100644 --- a/QemuVmManager.Services/VmManagementService.cs +++ b/QemuVmManager.Services/VmManagementService.cs @@ -442,16 +442,27 @@ public class VmManagementService await _processManager.StartPerformanceMonitoringAsync(vmName); } - public void StopPerformanceMonitoring(string vmName) + public async Task StopPerformanceMonitoringAsync(string vmName) { _processManager.StopPerformanceMonitoring(vmName); + await Task.CompletedTask; } - public bool IsPerformanceMonitoringActive(string vmName) + public async Task IsPerformanceMonitoringActiveAsync(string vmName) { return _processManager.GetAllVmStatuses().Any(s => s.Name == vmName && s.State == VmState.Running); } + public async Task GetCurrentPerformanceMetricsAsync(string vmName) + { + return await _processManager.GetVmPerformanceMetricsAsync(vmName); + } + + public async Task> GetPerformanceHistoryAsync(string vmName) + { + return await _processManager.GetPerformanceHistoryAsync(vmName); + } + public async Task GetVmPerformanceMetricsAsync(string vmName) { return await _processManager.GetVmPerformanceMetricsAsync(vmName);