P2P abilities

This commit is contained in:
2025-08-30 19:34:29 -04:00
parent eb00a5472f
commit 4a9047f31a
7 changed files with 2369 additions and 623 deletions

View 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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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<P2PNode>? _logger;
private readonly Dictionary<string, NodeInfo> _knownNodes = new();
private readonly Dictionary<string, VmInstance> _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<NodeRole>? RoleChanged;
public event EventHandler<NodeInfo>? NodeJoined;
public event EventHandler<NodeInfo>? NodeLeft;
public event EventHandler<VmInstance>? VmStarted;
public event EventHandler<VmInstance>? 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<P2PNode>? 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<VmInstance> 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<bool> 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<PortForwardingResponse> 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<VmMigrationResponse> 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<string, object>
{
["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<VmInstance> 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<bool> 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<VmInstance> 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<bool> RequestRemoteVmStopAsync(string vmId, string targetNodeId)
{
// This would implement RPC to remote node
throw new NotImplementedException("Remote VM stop not yet implemented");
}
private async Task<ElectionResponse> 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<JsonElement>(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<NodeRole>(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<int> 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<double> GetCpuUsageAsync()
{
// Simplified CPU usage calculation
return Environment.ProcessorCount * 0.1; // 10% per core
}
private async Task<double> 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();
}
}

View File

@@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -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<bool> IsUPnPAvailableAsync();
Task<IPAddress?> GetExternalIpAddressAsync();
Task<bool> AddPortMappingAsync(int externalPort, int internalPort, string description);
Task<bool> RemovePortMappingAsync(int externalPort);
Task<List<PortMapping>> GetPortMappingsAsync();
}
public class UPnPManager : IUPnPManager
{
private readonly ILogger<UPnPManager>? _logger;
private readonly Dictionary<int, PortMapping> _activeMappings = new();
private readonly object _lock = new();
public UPnPManager(ILogger<UPnPManager>? logger = null)
{
_logger = logger;
}
public async Task<bool> 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<IPAddress?> 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<bool> 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<bool> 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<List<PortMapping>> 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<List<UPnPDevice>> DiscoverUPnPDevicesAsync()
{
var devices = new List<UPnPDevice>();
// 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<IPAddress?> 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<IPAddress?> 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<bool> 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<bool> 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<List<PortMapping>> GetUPnPPortMappingsAsync()
{
try
{
var devices = await DiscoverUPnPDevicesAsync();
if (!devices.Any())
return new List<PortMapping>();
// This would require implementing SOAP calls to get port mappings
// For now, we'll return empty list
return new List<PortMapping>();
}
catch (Exception ex)
{
_logger?.LogError(ex, "Failed to get UPnP port mappings");
return new List<PortMapping>();
}
}
}
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; }
}

View File

@@ -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<string, object> Metadata { get; set; } = new();
public List<string> 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<NodeInfo> Nodes { get; set; } = new();
public DateTime LastUpdated { get; set; } = DateTime.UtcNow;
public Dictionary<string, VmInstance> 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<string, object> 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;
}

View File

@@ -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<bool> IsPerformanceMonitoringActiveAsync(string vmName)
{
return _processManager.GetAllVmStatuses().Any(s => s.Name == vmName && s.State == VmState.Running);
}
public async Task<VmPerformanceMetrics?> GetCurrentPerformanceMetricsAsync(string vmName)
{
return await _processManager.GetVmPerformanceMetricsAsync(vmName);
}
public async Task<List<VmPerformanceMetrics>> GetPerformanceHistoryAsync(string vmName)
{
return await _processManager.GetPerformanceHistoryAsync(vmName);
}
public async Task<VmPerformanceMetrics> GetVmPerformanceMetricsAsync(string vmName)
{
return await _processManager.GetVmPerformanceMetricsAsync(vmName);