Initial commit
This commit is contained in:
18
QemuVmManager.Services/QemuVmManager.Services.csproj
Normal file
18
QemuVmManager.Services/QemuVmManager.Services.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\QemuVmManager.Core\QemuVmManager.Core.csproj" />
|
||||
<ProjectReference Include="..\QemuVmManager.Models\QemuVmManager.Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
464
QemuVmManager.Services/VmManagementService.cs
Normal file
464
QemuVmManager.Services/VmManagementService.cs
Normal file
@@ -0,0 +1,464 @@
|
||||
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 void StopPerformanceMonitoring(string vmName)
|
||||
{
|
||||
_processManager.StopPerformanceMonitoring(vmName);
|
||||
}
|
||||
|
||||
public bool IsPerformanceMonitoringActive(string vmName)
|
||||
{
|
||||
return _processManager.GetAllVmStatuses().Any(s => s.Name == vmName && s.State == VmState.Running);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user