Files
skystack/QemuVmManager.Services/VmManagementService.cs

497 lines
15 KiB
C#

using System.Text.Json;
using QemuVmManager.Core;
using QemuVmManager.Models;
namespace QemuVmManager.Services;
public class VmManagementService
{
private readonly QemuProcessManager _processManager;
private readonly DiskManager _diskManager;
private readonly string _configDirectory;
private readonly Dictionary<string, VmConfiguration> _vmConfigurations = new();
public VmManagementService(string configDirectory = "vm-configs")
{
_processManager = new QemuProcessManager();
_diskManager = new DiskManager();
_configDirectory = configDirectory;
// Ensure config directory exists
Directory.CreateDirectory(_configDirectory);
// Subscribe to VM status changes
_processManager.VmStatusChanged += OnVmStatusChanged;
// Load existing configurations
LoadVmConfigurations();
}
public async Task<VmConfiguration> CreateVmAsync(VmConfiguration config)
{
// Validate configuration
ValidateConfiguration(config);
// Create disk images if they don't exist
await _diskManager.CreateDiskImagesForVmAsync(config);
// Save configuration
await SaveVmConfigurationAsync(config);
// Add to in-memory cache
_vmConfigurations[config.Name] = config;
return config;
}
public async Task<bool> StartVmAsync(string vmName)
{
if (!_vmConfigurations.TryGetValue(vmName, out var config))
{
throw new ArgumentException($"VM configuration '{vmName}' not found");
}
// Check if QEMU is installed
if (!_processManager.IsQemuInstalled())
{
throw new InvalidOperationException("QEMU is not installed or not found in PATH. Please install QEMU and ensure it's available in your system PATH.");
}
// Ensure disk images exist before starting
await _diskManager.CreateDiskImagesForVmAsync(config);
return await _processManager.StartVmAsync(config);
}
public async Task<bool> StopVmAsync(string vmName, bool force = false)
{
return await _processManager.StopVmAsync(vmName, force);
}
public async Task<bool> PauseVmAsync(string vmName)
{
return await _processManager.PauseVmAsync(vmName);
}
public async Task<bool> ResumeVmAsync(string vmName)
{
return await _processManager.ResumeVmAsync(vmName);
}
public async Task<VmConfiguration> UpdateVmAsync(string vmName, VmConfiguration updatedConfig)
{
if (!_vmConfigurations.ContainsKey(vmName))
{
throw new ArgumentException($"VM configuration '{vmName}' not found");
}
// Ensure name consistency
updatedConfig.Name = vmName;
updatedConfig.LastModified = DateTime.UtcNow;
// Validate configuration
ValidateConfiguration(updatedConfig);
// Save updated configuration
await SaveVmConfigurationAsync(updatedConfig);
// Update in-memory cache
_vmConfigurations[vmName] = updatedConfig;
return updatedConfig;
}
public async Task DeleteVmAsync(string vmName)
{
// Stop VM if running
if (_processManager.IsVmRunning(vmName))
{
await _processManager.StopVmAsync(vmName, true);
}
// Get configuration before removing from cache
var config = _vmConfigurations.GetValueOrDefault(vmName);
// Remove from in-memory cache
_vmConfigurations.Remove(vmName);
// Delete configuration file
var configPath = Path.Combine(_configDirectory, $"{vmName}.json");
if (File.Exists(configPath))
{
File.Delete(configPath);
}
// Delete disk images if they exist
if (config != null)
{
foreach (var disk in config.Storage.Disks)
{
_diskManager.DeleteDiskImage(disk.Path);
}
}
}
public VmConfiguration? GetVmConfiguration(string vmName)
{
return _vmConfigurations.TryGetValue(vmName, out var config) ? config : null;
}
public IEnumerable<VmConfiguration> GetAllVmConfigurations()
{
return _vmConfigurations.Values;
}
public VmStatus? GetVmStatus(string vmName)
{
return _processManager.GetVmStatus(vmName);
}
public IEnumerable<VmStatus> GetAllVmStatuses()
{
return _processManager.GetAllVmStatuses();
}
public bool IsVmRunning(string vmName)
{
return _processManager.IsVmRunning(vmName);
}
public async Task<VmConfiguration> CloneVmAsync(string sourceVmName, string newVmName, string? newDescription = null)
{
if (!_vmConfigurations.TryGetValue(sourceVmName, out var sourceConfig))
{
throw new ArgumentException($"Source VM configuration '{sourceVmName}' not found");
}
if (_vmConfigurations.ContainsKey(newVmName))
{
throw new ArgumentException($"VM configuration '{newVmName}' already exists");
}
// Create clone
var clonedConfig = CloneConfiguration(sourceConfig, newVmName, newDescription);
// Save cloned configuration
await SaveVmConfigurationAsync(clonedConfig);
// Add to in-memory cache
_vmConfigurations[newVmName] = clonedConfig;
return clonedConfig;
}
public async Task<string> ExportVmConfigurationAsync(string vmName, string exportPath)
{
if (!_vmConfigurations.TryGetValue(vmName, out var config))
{
throw new ArgumentException($"VM configuration '{vmName}' not found");
}
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions
{
WriteIndented = true
});
await File.WriteAllTextAsync(exportPath, json);
return exportPath;
}
public async Task<VmConfiguration> ImportVmConfigurationAsync(string importPath, string? newName = null)
{
if (!File.Exists(importPath))
{
throw new FileNotFoundException($"Configuration file '{importPath}' not found");
}
var json = await File.ReadAllTextAsync(importPath);
var config = JsonSerializer.Deserialize<VmConfiguration>(json);
if (config == null)
{
throw new InvalidOperationException("Failed to deserialize VM configuration");
}
// Use new name if provided
if (!string.IsNullOrEmpty(newName))
{
config.Name = newName;
}
// Validate and save
ValidateConfiguration(config);
// Create disk images if they don't exist
await _diskManager.CreateDiskImagesForVmAsync(config);
await SaveVmConfigurationAsync(config);
// Add to in-memory cache
_vmConfigurations[config.Name] = config;
return config;
}
// Disk management methods
public async Task<DiskInfo> GetDiskInfoAsync(string vmName, int diskIndex = 0)
{
if (!_vmConfigurations.TryGetValue(vmName, out var config))
{
throw new ArgumentException($"VM configuration '{vmName}' not found");
}
if (diskIndex >= config.Storage.Disks.Count)
{
throw new ArgumentException($"Disk index {diskIndex} is out of range");
}
var disk = config.Storage.Disks[diskIndex];
return await _diskManager.GetDiskInfoAsync(disk.Path, disk.Format);
}
public async Task<bool> ResizeDiskAsync(string vmName, int diskIndex, long newSizeGB)
{
if (!_vmConfigurations.TryGetValue(vmName, out var config))
{
throw new ArgumentException($"VM configuration '{vmName}' not found");
}
if (diskIndex >= config.Storage.Disks.Count)
{
throw new ArgumentException($"Disk index {diskIndex} is out of range");
}
var disk = config.Storage.Disks[diskIndex];
var success = await _diskManager.ResizeDiskAsync(disk.Path, disk.Format, newSizeGB);
if (success)
{
// Update the configuration
disk.Size = newSizeGB;
config.LastModified = DateTime.UtcNow;
await SaveVmConfigurationAsync(config);
}
return success;
}
public async Task<bool> ConvertDiskAsync(string vmName, int diskIndex, string newFormat)
{
if (!_vmConfigurations.TryGetValue(vmName, out var config))
{
throw new ArgumentException($"VM configuration '{vmName}' not found");
}
if (diskIndex >= config.Storage.Disks.Count)
{
throw new ArgumentException($"Disk index {diskIndex} is out of range");
}
var disk = config.Storage.Disks[diskIndex];
var newPath = Path.ChangeExtension(disk.Path, newFormat);
var success = await _diskManager.ConvertDiskAsync(disk.Path, disk.Format, newPath, newFormat);
if (success)
{
// Update the configuration
disk.Path = newPath;
disk.Format = newFormat;
config.LastModified = DateTime.UtcNow;
await SaveVmConfigurationAsync(config);
}
return success;
}
public bool ValidateDiskImages(string vmName)
{
if (!_vmConfigurations.TryGetValue(vmName, out var config))
{
throw new ArgumentException($"VM configuration '{vmName}' not found");
}
foreach (var disk in config.Storage.Disks)
{
if (!_diskManager.ValidateDiskImage(disk.Path, disk.Format))
{
return false;
}
}
return true;
}
private void ValidateConfiguration(VmConfiguration config)
{
if (string.IsNullOrWhiteSpace(config.Name))
{
throw new ArgumentException("VM name cannot be empty");
}
if (config.Cpu.Cores <= 0)
{
throw new ArgumentException("CPU cores must be greater than 0");
}
if (config.Memory.Size <= 0)
{
throw new ArgumentException("Memory size must be greater than 0");
}
if (config.Storage.Disks.Count == 0)
{
throw new ArgumentException("At least one disk must be configured");
}
// Validate disk paths
foreach (var disk in config.Storage.Disks)
{
if (string.IsNullOrWhiteSpace(disk.Path))
{
throw new ArgumentException("Disk path cannot be empty");
}
}
}
private async Task SaveVmConfigurationAsync(VmConfiguration config)
{
var configPath = Path.Combine(_configDirectory, $"{config.Name}.json");
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions
{
WriteIndented = true
});
await File.WriteAllTextAsync(configPath, json);
}
private void LoadVmConfigurations()
{
if (!Directory.Exists(_configDirectory))
{
return;
}
var configFiles = Directory.GetFiles(_configDirectory, "*.json");
foreach (var configFile in configFiles)
{
try
{
var json = File.ReadAllText(configFile);
var config = JsonSerializer.Deserialize<VmConfiguration>(json);
if (config != null && !string.IsNullOrWhiteSpace(config.Name))
{
_vmConfigurations[config.Name] = config;
}
}
catch (Exception ex)
{
// Log error but continue loading other configurations
Console.WriteLine($"Failed to load configuration from {configFile}: {ex.Message}");
}
}
}
private VmConfiguration CloneConfiguration(VmConfiguration source, string newName, string? newDescription)
{
var json = JsonSerializer.Serialize(source);
var cloned = JsonSerializer.Deserialize<VmConfiguration>(json);
if (cloned == null)
{
throw new InvalidOperationException("Failed to clone configuration");
}
cloned.Name = newName;
cloned.Description = newDescription ?? $"Clone of {source.Name}";
cloned.Created = DateTime.UtcNow;
cloned.LastModified = DateTime.UtcNow;
// Update disk paths to avoid conflicts
for (int i = 0; i < cloned.Storage.Disks.Count; i++)
{
var disk = cloned.Storage.Disks[i];
var directory = Path.GetDirectoryName(disk.Path);
var fileName = Path.GetFileName(disk.Path);
var extension = Path.GetExtension(fileName);
var nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
disk.Path = Path.Combine(directory ?? "", $"{nameWithoutExtension}_{newName}{extension}");
}
return cloned;
}
private void OnVmStatusChanged(object? sender, VmStatusChangedEventArgs e)
{
// Log status changes
Console.WriteLine($"VM '{e.VmName}' status changed from {e.OldState} to {e.NewState}");
// You could add additional logic here, such as:
// - Sending notifications
// - Updating a database
// - Triggering automated actions
}
// Performance monitoring methods
public async Task StartPerformanceMonitoringAsync(string vmName)
{
await _processManager.StartPerformanceMonitoringAsync(vmName);
}
public async Task StopPerformanceMonitoringAsync(string vmName)
{
_processManager.StopPerformanceMonitoring(vmName);
await Task.CompletedTask;
}
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);
}
public async Task<List<VmPerformanceMetrics>> GetPerformanceHistoryAsync(string vmName, int maxSamples = 100)
{
return await _processManager.GetPerformanceHistoryAsync(vmName, maxSamples);
}
public async Task UpdateVmConfigurationAsync(string vmName, VmConfiguration updatedConfig)
{
if (!_vmConfigurations.ContainsKey(vmName))
{
throw new ArgumentException($"VM configuration '{vmName}' not found");
}
// Ensure name consistency
updatedConfig.Name = vmName;
updatedConfig.LastModified = DateTime.UtcNow;
// Validate configuration
ValidateConfiguration(updatedConfig);
// Save updated configuration
await SaveVmConfigurationAsync(updatedConfig);
// Update in-memory cache
_vmConfigurations[vmName] = updatedConfig;
}
}