P2P abilities
This commit is contained in:
521
QemuVmManager.Console/P2PConsole.cs
Normal file
521
QemuVmManager.Console/P2PConsole.cs
Normal file
@@ -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<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 "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(" 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user