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 _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 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 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 StopVmAsync(string vmName, bool force = false) { return await _processManager.StopVmAsync(vmName, force); } public async Task PauseVmAsync(string vmName) { return await _processManager.PauseVmAsync(vmName); } public async Task ResumeVmAsync(string vmName) { return await _processManager.ResumeVmAsync(vmName); } public async Task 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 GetAllVmConfigurations() { return _vmConfigurations.Values; } public VmStatus? GetVmStatus(string vmName) { return _processManager.GetVmStatus(vmName); } public IEnumerable GetAllVmStatuses() { return _processManager.GetAllVmStatuses(); } public bool IsVmRunning(string vmName) { return _processManager.IsVmRunning(vmName); } public async Task 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 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 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(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 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 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 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(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(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 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); } public async Task> 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; } }