Initial commit

This commit is contained in:
2025-08-30 18:55:20 -04:00
commit eb00a5472f
19 changed files with 4263 additions and 0 deletions

380
.gitignore vendored Normal file
View File

@@ -0,0 +1,380 @@
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these files may be visible to others.
*.azurePubxml
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment the next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
CConversionReportFiles/
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
# VM Configuration files (user data)
vm-configs/
# QEMU disk images (user data)
*.qcow2
*.raw
*.vmdk
*.vdi
*.vhd
*.vhdx
# Temporary files
*.tmp
*.temp

95
QUICKSTART.md Normal file
View File

@@ -0,0 +1,95 @@
# Quick Start Guide
## Prerequisites
1. **Install .NET 8.0 SDK**
- Download from: https://dotnet.microsoft.com/download
- Verify installation: `dotnet --version`
2. **Install QEMU**
- **Windows**: Download from https://qemu.weilnetz.de/
- **Linux**: `sudo apt install qemu-system-x86` (Ubuntu/Debian)
- **macOS**: `brew install qemu`
3. **For KVM acceleration (Linux only)**:
```bash
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
sudo usermod -aG kvm $USER
sudo usermod -aG libvirt $USER
```
## Quick Start
1. **Clone and build**:
```bash
git clone <repository-url>
cd skystack
dotnet build
```
2. **Run the application**:
```bash
dotnet run --project QemuVmManager.Console
```
3. **Create your first VM**:
```
qemu-vm> create my-first-vm
Enter VM name: my-first-vm
Description (optional): My first VM
CPU cores (2): 2
CPU model (qemu64): qemu64
Memory size in MB (2048): 2048
Disk path: /path/to/your/disk.qcow2
Disk size in GB (10): 10
Disk format (qcow2): qcow2
Disk interface (virtio): virtio
Network bridge (virbr0): virbr0
Display type (gtk): gtk
VGA type (virtio): virtio
```
4. **Start the VM**:
```
qemu-vm> start my-first-vm
```
5. **Check status**:
```
qemu-vm> status my-first-vm
```
## Common Commands
- `list` - Show all VMs
- `start <name>` - Start a VM
- `stop <name>` - Stop a VM
- `pause <name>` - Pause a VM
- `resume <name>` - Resume a VM
- `delete <name>` - Delete a VM
- `help` - Show all commands
## Troubleshooting
### QEMU not found
- Ensure QEMU is installed and in your PATH
- Windows: Add QEMU installation directory to PATH
### Permission denied (Linux)
- Add your user to kvm and libvirt groups
- Restart your session after adding groups
### Network bridge not found
- Create the bridge: `sudo virsh net-start default`
- Or use user networking: change bridge to "user" in VM config
### Build issues
- Ensure .NET 8.0 SDK is installed
- Run `dotnet --version` to verify
- Try `dotnet restore` before building
## Next Steps
1. Read the full [README.md](README.md) for detailed documentation
2. Check the [examples/](examples/) directory for sample configurations
3. Explore advanced features like SPICE remote desktop and shared folders

View File

@@ -0,0 +1,904 @@
using QemuVmManager.Services;
using QemuVmManager.Models;
namespace QemuVmManager.Console;
class Program
{
private static VmManagementService _vmService = null!;
static async Task Main(string[] args)
{
try
{
_vmService = new VmManagementService();
System.Console.WriteLine("=== QEMU VM Manager ===");
System.Console.WriteLine("Type 'help' for available commands");
System.Console.WriteLine();
await RunInteractiveMode();
}
catch (Exception ex)
{
System.Console.WriteLine($"Error: {ex.Message}");
System.Console.WriteLine("Press any key to exit...");
System.Console.ReadKey();
}
}
static async Task RunInteractiveMode()
{
while (true)
{
try
{
System.Console.Write("qemu-vm> ");
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 "list":
await ListVms();
break;
case "create":
await CreateVm(arguments);
break;
case "start":
await StartVm(arguments);
break;
case "stop":
await StopVm(arguments);
break;
case "pause":
await PauseVm(arguments);
break;
case "resume":
await ResumeVm(arguments);
break;
case "delete":
await DeleteVm(arguments);
break;
case "clone":
await CloneVm(arguments);
break;
case "export":
await ExportVm(arguments);
break;
case "import":
await ImportVm(arguments);
break;
case "status":
await ShowVmStatus(arguments);
break;
case "config":
await ShowVmConfig(arguments);
break;
case "disk":
await ManageDisk(arguments);
break;
case "validate":
await ValidateVm(arguments);
break;
case "diagnose":
await DiagnoseSystem();
break;
case "monitor":
await MonitorPerformance(arguments);
break;
case "metrics":
await ShowMetrics(arguments);
break;
case "exit":
case "quit":
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();
}
}
static void ShowHelp()
{
System.Console.WriteLine("Available commands:");
System.Console.WriteLine(" list - List all VMs");
System.Console.WriteLine(" create <name> - Create a new VM (interactive)");
System.Console.WriteLine(" start <name> - Start a VM");
System.Console.WriteLine(" stop <name> [--force] - Stop a VM");
System.Console.WriteLine(" pause <name> - Pause a VM");
System.Console.WriteLine(" resume <name> - Resume a VM");
System.Console.WriteLine(" delete <name> - Delete a VM");
System.Console.WriteLine(" clone <source> <target> - Clone a VM");
System.Console.WriteLine(" export <name> <path> - Export VM configuration");
System.Console.WriteLine(" import <path> [name] - Import VM configuration");
System.Console.WriteLine(" status [name] - Show VM status");
System.Console.WriteLine(" config <name> - Show VM configuration");
System.Console.WriteLine(" disk <name> [info|resize|convert] - Manage disk images");
System.Console.WriteLine(" validate <name> - Validate VM disk images");
System.Console.WriteLine(" diagnose - Diagnose system and QEMU installation");
System.Console.WriteLine(" monitor <name> [start|stop|status] - Performance monitoring");
System.Console.WriteLine(" metrics <name> [current|history] - Show performance metrics");
System.Console.WriteLine(" help - Show this help");
System.Console.WriteLine(" exit/quit - Exit the application");
}
static Task ListVms()
{
var configs = _vmService.GetAllVmConfigurations().ToList();
var statuses = _vmService.GetAllVmStatuses().ToDictionary(s => s.Name);
if (configs.Count == 0)
{
System.Console.WriteLine("No VMs configured.");
return Task.CompletedTask;
}
System.Console.WriteLine($"{"Name",-20} {"Status",-10} {"CPU",-8} {"Memory",-10} {"Description"}");
System.Console.WriteLine(new string('-', 80));
foreach (var config in configs)
{
var status = statuses.GetValueOrDefault(config.Name);
var statusText = status?.State.ToString() ?? "Unknown";
var cpuText = $"{config.Cpu.Cores} cores";
var memoryText = $"{config.Memory.Size}{config.Memory.Unit}";
System.Console.WriteLine($"{config.Name,-20} {statusText,-10} {cpuText,-8} {memoryText,-10} {config.Description}");
}
return Task.CompletedTask;
}
static async Task CreateVm(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 = "bridge",
Model = "virtio-net-pci",
Bridge = GetUserInput("Network bridge (virbr0): ", "virbr0")
}
}
},
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.");
}
static async Task StartVm(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: start <vm-name>");
return;
}
var vmName = arguments[0];
var success = await _vmService.StartVmAsync(vmName);
if (success)
System.Console.WriteLine($"VM '{vmName}' started successfully.");
else
System.Console.WriteLine($"Failed to start VM '{vmName}'.");
}
static async Task StopVm(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: stop <vm-name> [--force]");
return;
}
var vmName = arguments[0];
var force = arguments.Contains("--force");
var success = await _vmService.StopVmAsync(vmName, force);
if (success)
System.Console.WriteLine($"VM '{vmName}' stopped successfully.");
else
System.Console.WriteLine($"Failed to stop VM '{vmName}'.");
}
static async Task PauseVm(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: pause <vm-name>");
return;
}
var vmName = arguments[0];
var success = await _vmService.PauseVmAsync(vmName);
if (success)
System.Console.WriteLine($"VM '{vmName}' paused successfully.");
else
System.Console.WriteLine($"Failed to pause VM '{vmName}'.");
}
static async Task ResumeVm(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: resume <vm-name>");
return;
}
var vmName = arguments[0];
var success = await _vmService.ResumeVmAsync(vmName);
if (success)
System.Console.WriteLine($"VM '{vmName}' resumed successfully.");
else
System.Console.WriteLine($"Failed to resume VM '{vmName}'.");
}
static async Task DeleteVm(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: delete <vm-name>");
return;
}
var vmName = arguments[0];
System.Console.Write($"Are you sure you want to delete VM '{vmName}'? (y/N): ");
var confirm = System.Console.ReadLine()?.Trim().ToLower();
if (confirm == "y" || confirm == "yes")
{
await _vmService.DeleteVmAsync(vmName);
System.Console.WriteLine($"VM '{vmName}' deleted successfully.");
}
else
{
System.Console.WriteLine("Deletion cancelled.");
}
}
static async Task CloneVm(string[] arguments)
{
if (arguments.Length < 2)
{
System.Console.WriteLine("Usage: clone <source-vm> <target-vm>");
return;
}
var sourceVm = arguments[0];
var targetVm = arguments[1];
var clonedConfig = await _vmService.CloneVmAsync(sourceVm, targetVm);
System.Console.WriteLine($"VM '{sourceVm}' cloned to '{targetVm}' successfully.");
}
static async Task ExportVm(string[] arguments)
{
if (arguments.Length < 2)
{
System.Console.WriteLine("Usage: export <vm-name> <export-path>");
return;
}
var vmName = arguments[0];
var exportPath = arguments[1];
var exportedPath = await _vmService.ExportVmConfigurationAsync(vmName, exportPath);
System.Console.WriteLine($"VM '{vmName}' exported to '{exportedPath}' successfully.");
}
static async Task ImportVm(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: import <import-path> [new-name]");
return;
}
var importPath = arguments[0];
var newName = arguments.Length > 1 ? arguments[1] : null;
var importedConfig = await _vmService.ImportVmConfigurationAsync(importPath, newName);
System.Console.WriteLine($"VM '{importedConfig.Name}' imported successfully.");
}
static Task ShowVmStatus(string[] arguments)
{
if (arguments.Length == 0)
{
// Show all VM statuses
var statuses = _vmService.GetAllVmStatuses();
if (!statuses.Any())
{
System.Console.WriteLine("No VMs found.");
return Task.CompletedTask;
}
System.Console.WriteLine($"{"Name",-20} {"Status",-10} {"PID",-8} {"Started",-20} {"Error"}");
System.Console.WriteLine(new string('-', 80));
foreach (var status in statuses)
{
var pidText = status.ProcessId > 0 ? status.ProcessId.ToString() : "-";
var startedText = status.StartedAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-";
var errorText = status.ErrorMessage ?? "";
System.Console.WriteLine($"{status.Name,-20} {status.State,-10} {pidText,-8} {startedText,-20} {errorText}");
}
}
else
{
// Show specific VM status
var vmName = arguments[0];
var status = _vmService.GetVmStatus(vmName);
if (status == null)
{
System.Console.WriteLine($"VM '{vmName}' not found.");
return Task.CompletedTask;
}
System.Console.WriteLine($"VM: {status.Name}");
System.Console.WriteLine($"Status: {status.State}");
System.Console.WriteLine($"Process ID: {status.ProcessId}");
System.Console.WriteLine($"Started: {status.StartedAt}");
System.Console.WriteLine($"Stopped: {status.StoppedAt}");
if (status.ResourceUsage != null)
{
System.Console.WriteLine($"CPU Usage: {status.ResourceUsage.CpuUsage:F1}%");
System.Console.WriteLine($"Memory Usage: {status.ResourceUsage.MemoryUsage} MB");
}
if (!string.IsNullOrEmpty(status.ErrorMessage))
{
System.Console.WriteLine($"Error: {status.ErrorMessage}");
}
}
return Task.CompletedTask;
}
static Task ShowVmConfig(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: config <vm-name>");
return Task.CompletedTask;
}
var vmName = arguments[0];
var config = _vmService.GetVmConfiguration(vmName);
if (config == null)
{
System.Console.WriteLine($"VM '{vmName}' not found.");
return Task.CompletedTask;
}
System.Console.WriteLine($"VM Configuration: {config.Name}");
System.Console.WriteLine($"Description: {config.Description}");
System.Console.WriteLine($"Created: {config.Created}");
System.Console.WriteLine($"Modified: {config.LastModified}");
System.Console.WriteLine();
System.Console.WriteLine("CPU Configuration:");
System.Console.WriteLine($" Cores: {config.Cpu.Cores}");
System.Console.WriteLine($" Model: {config.Cpu.Model}");
System.Console.WriteLine($" KVM: {config.Cpu.EnableKvm}");
System.Console.WriteLine();
System.Console.WriteLine("Memory Configuration:");
System.Console.WriteLine($" Size: {config.Memory.Size}{config.Memory.Unit}");
System.Console.WriteLine();
System.Console.WriteLine("Storage Configuration:");
foreach (var disk in config.Storage.Disks)
{
System.Console.WriteLine($" Disk: {disk.Path}");
System.Console.WriteLine($" Size: {disk.Size} GB");
System.Console.WriteLine($" Format: {disk.Format}");
System.Console.WriteLine($" Interface: {disk.Interface}");
System.Console.WriteLine($" Boot: {disk.IsBoot}");
}
if (!string.IsNullOrEmpty(config.Storage.Cdrom))
{
System.Console.WriteLine($" CD-ROM: {config.Storage.Cdrom}");
}
System.Console.WriteLine();
System.Console.WriteLine("Network Configuration:");
foreach (var nic in config.Network.Interfaces)
{
System.Console.WriteLine($" Interface: {nic.Model}");
System.Console.WriteLine($" Type: {nic.Type}");
System.Console.WriteLine($" Bridge: {nic.Bridge}");
if (!string.IsNullOrEmpty(nic.Mac))
{
System.Console.WriteLine($" MAC: {nic.Mac}");
}
}
System.Console.WriteLine();
System.Console.WriteLine("Display Configuration:");
System.Console.WriteLine($" Type: {config.Display.Type}");
System.Console.WriteLine($" VGA: {config.Display.Vga}");
System.Console.WriteLine($" SPICE: {config.Display.EnableSpice}");
if (config.Display.EnableSpice)
{
System.Console.WriteLine($" SPICE Port: {config.Display.SpicePort}");
}
return Task.CompletedTask;
}
static async Task ManageDisk(string[] arguments)
{
if (arguments.Length < 1)
{
System.Console.WriteLine("Usage: disk <vm-name> [info|resize|convert]");
return;
}
var vmName = arguments[0];
var action = arguments.Length > 1 ? arguments[1].ToLower() : "info";
switch (action)
{
case "info":
await ShowDiskInfo(vmName);
break;
case "resize":
await ResizeDisk(vmName, arguments.Skip(2).ToArray());
break;
case "convert":
await ConvertDisk(vmName, arguments.Skip(2).ToArray());
break;
default:
System.Console.WriteLine($"Unknown disk action: {action}");
System.Console.WriteLine("Available actions: info, resize, convert");
break;
}
}
static async Task ShowDiskInfo(string vmName)
{
try
{
var config = _vmService.GetVmConfiguration(vmName);
if (config == null)
{
System.Console.WriteLine($"VM '{vmName}' not found.");
return;
}
System.Console.WriteLine($"Disk Information for VM: {vmName}");
System.Console.WriteLine(new string('-', 50));
for (int i = 0; i < config.Storage.Disks.Count; i++)
{
var disk = config.Storage.Disks[i];
System.Console.WriteLine($"Disk {i}:");
System.Console.WriteLine($" Path: {disk.Path}");
System.Console.WriteLine($" Format: {disk.Format}");
System.Console.WriteLine($" Size: {disk.Size} GB");
System.Console.WriteLine($" Interface: {disk.Interface}");
System.Console.WriteLine($" Boot: {disk.IsBoot}");
var diskInfo = await _vmService.GetDiskInfoAsync(vmName, i);
if (diskInfo.Exists)
{
System.Console.WriteLine($" Virtual Size: {diskInfo.VirtualSize}");
System.Console.WriteLine($" Disk Size: {diskInfo.DiskSize}");
System.Console.WriteLine($" Format: {diskInfo.Format}");
}
else
{
System.Console.WriteLine(" Status: Not found");
}
System.Console.WriteLine();
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error: {ex.Message}");
}
}
static async Task ResizeDisk(string vmName, string[] arguments)
{
if (arguments.Length < 2)
{
System.Console.WriteLine("Usage: disk <vm-name> resize <disk-index> <new-size-gb>");
return;
}
try
{
var diskIndex = int.Parse(arguments[0]);
var newSizeGB = long.Parse(arguments[1]);
var success = await _vmService.ResizeDiskAsync(vmName, diskIndex, newSizeGB);
if (success)
{
System.Console.WriteLine($"Disk {diskIndex} resized to {newSizeGB} GB successfully.");
}
else
{
System.Console.WriteLine("Failed to resize disk.");
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error: {ex.Message}");
}
}
static async Task ConvertDisk(string vmName, string[] arguments)
{
if (arguments.Length < 2)
{
System.Console.WriteLine("Usage: disk <vm-name> convert <disk-index> <new-format>");
System.Console.WriteLine("Available formats: qcow2, raw, vmdk, vdi, vhd");
return;
}
try
{
var diskIndex = int.Parse(arguments[0]);
var newFormat = arguments[1];
var success = await _vmService.ConvertDiskAsync(vmName, diskIndex, newFormat);
if (success)
{
System.Console.WriteLine($"Disk {diskIndex} converted to {newFormat} format successfully.");
}
else
{
System.Console.WriteLine("Failed to convert disk.");
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error: {ex.Message}");
}
}
static async Task ValidateVm(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: validate <vm-name>");
return;
}
var vmName = arguments[0];
try
{
var isValid = _vmService.ValidateDiskImages(vmName);
if (isValid)
{
System.Console.WriteLine($"VM '{vmName}' disk images are valid.");
}
else
{
System.Console.WriteLine($"VM '{vmName}' has invalid disk images.");
System.Console.WriteLine("Use 'disk <vm-name> info' to check disk status.");
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error: {ex.Message}");
}
}
static async Task DiagnoseSystem()
{
System.Console.WriteLine("=== System Diagnosis ===");
System.Console.WriteLine();
// Check .NET version
System.Console.WriteLine("1. .NET Runtime:");
System.Console.WriteLine($" Version: {Environment.Version}");
System.Console.WriteLine($" OS: {Environment.OSVersion}");
System.Console.WriteLine($" Architecture: {Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")}");
System.Console.WriteLine();
// Check QEMU installation and virtualization
System.Console.WriteLine("2. QEMU Installation:");
try
{
var processManager = new QemuVmManager.Core.QemuProcessManager();
var isInstalled = processManager.IsQemuInstalled();
var version = processManager.GetQemuVersion();
var accelerators = processManager.GetQemuAccelerators();
var virtualizationEnabled = processManager.IsVirtualizationEnabled();
var availableVirtualization = processManager.GetAvailableVirtualization();
System.Console.WriteLine($" Installed: {(isInstalled ? "Yes" : "No")}");
System.Console.WriteLine($" Version: {version}");
System.Console.WriteLine($" Available Accelerators:");
foreach (var line in accelerators.Split('\n', StringSplitOptions.RemoveEmptyEntries))
{
System.Console.WriteLine($" {line.Trim()}");
}
System.Console.WriteLine($" Virtualization Enabled in BIOS: {(virtualizationEnabled ? "Yes" : "No")}");
System.Console.WriteLine($" Available Virtualization: {availableVirtualization}");
if (!isInstalled)
{
System.Console.WriteLine(" ❌ QEMU is not installed or not found in PATH");
System.Console.WriteLine(" Please install QEMU and ensure it's available in your system PATH");
System.Console.WriteLine(" Windows: Download from https://qemu.weilnetz.de/");
System.Console.WriteLine(" Linux: sudo apt-get install qemu-system-x86_64");
System.Console.WriteLine(" macOS: brew install qemu");
}
else if (!virtualizationEnabled)
{
System.Console.WriteLine(" ⚠️ Virtualization is not enabled in BIOS/UEFI");
System.Console.WriteLine(" Please enable VT-x (Intel) or AMD-V (AMD) in your BIOS settings");
System.Console.WriteLine(" This will significantly improve VM performance");
}
else if (availableVirtualization == VirtualizationType.TCG)
{
System.Console.WriteLine(" ⚠️ Only software emulation (TCG) is available");
System.Console.WriteLine(" Hardware virtualization is not available or not properly configured");
System.Console.WriteLine(" This may be due to:");
System.Console.WriteLine(" - QEMU build not supporting hardware acceleration");
System.Console.WriteLine(" - Missing virtualization drivers");
System.Console.WriteLine(" - Hyper-V or other virtualization software conflicts");
}
else
{
System.Console.WriteLine($" ✅ Hardware virtualization ({availableVirtualization}) is available");
}
}
catch (Exception ex)
{
System.Console.WriteLine($" ❌ Error checking QEMU: {ex.Message}");
}
System.Console.WriteLine();
// Check disk manager
System.Console.WriteLine("3. Disk Manager:");
try
{
var diskManager = new QemuVmManager.Core.DiskManager();
System.Console.WriteLine(" ✅ Disk manager initialized successfully");
}
catch (Exception ex)
{
System.Console.WriteLine($" ❌ Error initializing disk manager: {ex.Message}");
}
System.Console.WriteLine();
// Check VM configurations
System.Console.WriteLine("4. VM Configurations:");
try
{
var configs = _vmService.GetAllVmConfigurations().ToList();
System.Console.WriteLine($" Found {configs.Count} VM configuration(s)");
foreach (var config in configs)
{
System.Console.WriteLine($" - {config.Name}: {config.Description}");
// Check disk images
foreach (var disk in config.Storage.Disks)
{
var exists = File.Exists(disk.Path);
System.Console.WriteLine($" Disk: {disk.Path} - {(exists ? " Exists" : " Missing")}");
}
}
}
catch (Exception ex)
{
System.Console.WriteLine($" ❌ Error checking VM configurations: {ex.Message}");
}
System.Console.WriteLine();
// Check running VMs
System.Console.WriteLine("5. Running VMs:");
try
{
var statuses = _vmService.GetAllVmStatuses().ToList();
var runningVms = statuses.Where(s => s.State == QemuVmManager.Models.VmState.Running).ToList();
System.Console.WriteLine($" Running: {runningVms.Count}");
foreach (var vm in runningVms)
{
System.Console.WriteLine($" - {vm.Name} (PID: {vm.ProcessId})");
}
}
catch (Exception ex)
{
System.Console.WriteLine($" ❌ Error checking running VMs: {ex.Message}");
}
System.Console.WriteLine();
System.Console.WriteLine("=== Diagnosis Complete ===");
}
static string GetUserInput(string prompt, string defaultValue = "")
{
System.Console.Write(prompt);
var input = System.Console.ReadLine()?.Trim();
return string.IsNullOrEmpty(input) ? defaultValue : input;
}
static async Task MonitorPerformance(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: monitor <vm-name> [start|stop|status]");
return;
}
var vmName = arguments[0];
var action = arguments.Length > 1 ? arguments[1].ToLower() : "status";
try
{
switch (action)
{
case "start":
await _vmService.StartPerformanceMonitoringAsync(vmName);
System.Console.WriteLine($"Performance monitoring started for VM '{vmName}'");
break;
case "stop":
_vmService.StopPerformanceMonitoring(vmName);
System.Console.WriteLine($"Performance monitoring stopped for VM '{vmName}'");
break;
case "status":
default:
var isMonitoring = _vmService.IsPerformanceMonitoringActive(vmName);
System.Console.WriteLine($"Performance monitoring for VM '{vmName}': {(isMonitoring ? "Active" : "Inactive")}");
break;
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error: {ex.Message}");
}
}
static async Task ShowMetrics(string[] arguments)
{
if (arguments.Length == 0)
{
System.Console.WriteLine("Usage: metrics <vm-name> [current|history]");
return;
}
var vmName = arguments[0];
var type = arguments.Length > 1 ? arguments[1].ToLower() : "current";
try
{
switch (type)
{
case "current":
var currentMetrics = await _vmService.GetVmPerformanceMetricsAsync(vmName);
DisplayPerformanceMetrics(currentMetrics, "Current");
break;
case "history":
var history = await _vmService.GetPerformanceHistoryAsync(vmName, 20);
System.Console.WriteLine($"Performance History for VM '{vmName}' (Last {history.Count} samples):");
System.Console.WriteLine();
foreach (var metrics in history.TakeLast(10))
{
DisplayPerformanceMetrics(metrics, metrics.Timestamp.ToString("HH:mm:ss"));
}
break;
default:
System.Console.WriteLine("Invalid metrics type. Use 'current' or 'history'");
break;
}
}
catch (Exception ex)
{
System.Console.WriteLine($"Error: {ex.Message}");
}
}
static void DisplayPerformanceMetrics(VmPerformanceMetrics metrics, string label)
{
System.Console.WriteLine($"=== {label} Performance Metrics ===");
System.Console.WriteLine($"Timestamp: {metrics.Timestamp:yyyy-MM-dd HH:mm:ss}");
System.Console.WriteLine($"Process ID: {metrics.ProcessId}");
System.Console.WriteLine();
System.Console.WriteLine("CPU Usage:");
System.Console.WriteLine($" VM CPU: {metrics.CpuUsagePercent:F2}%");
System.Console.WriteLine($" System CPU: {metrics.SystemCpuUsagePercent:F2}%");
System.Console.WriteLine();
System.Console.WriteLine("Memory Usage:");
System.Console.WriteLine($" Working Set: {metrics.MemoryUsageMB:N0} MB");
System.Console.WriteLine($" Private Memory: {metrics.PrivateMemoryMB:N0} MB");
System.Console.WriteLine($" Virtual Memory: {metrics.VirtualMemoryMB:N0} MB");
System.Console.WriteLine();
System.Console.WriteLine("Process Info:");
System.Console.WriteLine($" Threads: {metrics.ThreadCount}");
System.Console.WriteLine($" Handles: {metrics.HandleCount}");
System.Console.WriteLine();
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\QemuVmManager.Services\QemuVmManager.Services.csproj" />
<ProjectReference Include="..\QemuVmManager.Models\QemuVmManager.Models.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,312 @@
using System.Diagnostics;
using QemuVmManager.Models;
namespace QemuVmManager.Core;
public class DiskManager
{
private readonly string _diskDirectory;
public DiskManager(string diskDirectory = "vm-disks")
{
_diskDirectory = diskDirectory;
Directory.CreateDirectory(_diskDirectory);
}
public async Task<bool> CreateDiskImageAsync(DiskConfiguration diskConfig)
{
try
{
// Ensure the directory exists
var directory = Path.GetDirectoryName(diskConfig.Path);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// Check if disk already exists
if (File.Exists(diskConfig.Path))
{
return true; // Disk already exists
}
// Create disk image using qemu-img
var sizeInBytes = diskConfig.Size * 1024 * 1024 * 1024; // Convert GB to bytes
var sizeInMB = diskConfig.Size * 1024; // Convert GB to MB for qemu-img
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-img",
Arguments = $"create -f {diskConfig.Format} \"{diskConfig.Path}\" {sizeInMB}M",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
var started = process.Start();
if (!started)
{
throw new InvalidOperationException("Failed to start qemu-img process");
}
await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
var error = await process.StandardError.ReadToEndAsync();
throw new InvalidOperationException($"Failed to create disk image: {error}");
}
return true;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to create disk image '{diskConfig.Path}': {ex.Message}", ex);
}
}
public async Task<bool> CreateDiskImagesForVmAsync(VmConfiguration vmConfig)
{
try
{
foreach (var disk in vmConfig.Storage.Disks)
{
await CreateDiskImageAsync(disk);
}
return true;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to create disk images for VM '{vmConfig.Name}': {ex.Message}", ex);
}
}
public bool ValidateDiskImage(string diskPath, string format)
{
try
{
if (!File.Exists(diskPath))
{
return false;
}
// Use qemu-img info to validate the disk
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-img",
Arguments = $"info -f {format} \"{diskPath}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
var started = process.Start();
if (!started)
{
return false;
}
process.WaitForExit();
return process.ExitCode == 0;
}
catch
{
return false;
}
}
public async Task<DiskInfo> GetDiskInfoAsync(string diskPath, string format)
{
try
{
if (!File.Exists(diskPath))
{
return new DiskInfo { Exists = false };
}
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-img",
Arguments = $"info -f {format} \"{diskPath}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
var started = process.Start();
if (!started)
{
return new DiskInfo { Exists = false };
}
await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
return new DiskInfo { Exists = false };
}
var output = await process.StandardOutput.ReadToEndAsync();
return ParseDiskInfo(output, diskPath);
}
catch
{
return new DiskInfo { Exists = false };
}
}
private DiskInfo ParseDiskInfo(string qemuImgOutput, string diskPath)
{
var info = new DiskInfo
{
Path = diskPath,
Exists = true
};
var lines = qemuImgOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var trimmedLine = line.Trim();
if (trimmedLine.StartsWith("virtual size:"))
{
var sizePart = trimmedLine.Substring("virtual size:".Length).Trim();
info.VirtualSize = sizePart;
}
else if (trimmedLine.StartsWith("disk size:"))
{
var sizePart = trimmedLine.Substring("disk size:".Length).Trim();
info.DiskSize = sizePart;
}
else if (trimmedLine.StartsWith("format:"))
{
var formatPart = trimmedLine.Substring("format:".Length).Trim();
info.Format = formatPart;
}
}
return info;
}
public async Task<bool> ResizeDiskAsync(string diskPath, string format, long newSizeGB)
{
try
{
if (!File.Exists(diskPath))
{
throw new FileNotFoundException($"Disk image not found: {diskPath}");
}
var newSizeMB = newSizeGB * 1024; // Convert GB to MB
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-img",
Arguments = $"resize -f {format} \"{diskPath}\" {newSizeMB}M",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
var started = process.Start();
if (!started)
{
throw new InvalidOperationException("Failed to start qemu-img process");
}
await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
var error = await process.StandardError.ReadToEndAsync();
throw new InvalidOperationException($"Failed to resize disk image: {error}");
}
return true;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to resize disk image '{diskPath}': {ex.Message}", ex);
}
}
public async Task<bool> ConvertDiskAsync(string sourcePath, string sourceFormat, string targetPath, string targetFormat)
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-img",
Arguments = $"convert -f {sourceFormat} -O {targetFormat} \"{sourcePath}\" \"{targetPath}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
var started = process.Start();
if (!started)
{
throw new InvalidOperationException("Failed to start qemu-img process");
}
await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
var error = await process.StandardError.ReadToEndAsync();
throw new InvalidOperationException($"Failed to convert disk image: {error}");
}
return true;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to convert disk image: {ex.Message}", ex);
}
}
public bool DeleteDiskImage(string diskPath)
{
try
{
if (File.Exists(diskPath))
{
File.Delete(diskPath);
return true;
}
return false;
}
catch
{
return false;
}
}
}
public class DiskInfo
{
public string Path { get; set; } = string.Empty;
public bool Exists { get; set; }
public string? VirtualSize { get; set; }
public string? DiskSize { get; set; }
public string? Format { get; set; }
}

View File

@@ -0,0 +1,356 @@
using QemuVmManager.Models;
using System.Diagnostics;
namespace QemuVmManager.Core;
public class QemuCommandBuilder
{
private readonly VmConfiguration _config;
private readonly List<string> _arguments = new();
private readonly VirtualizationType _virtualizationType;
public QemuCommandBuilder(VmConfiguration config, VirtualizationType virtualizationType = VirtualizationType.TCG)
{
_config = config;
_virtualizationType = virtualizationType;
}
public string BuildCommand()
{
_arguments.Clear();
// Basic QEMU command
_arguments.Add("qemu-system-x86_64");
// Machine and CPU configuration
AddMachineConfiguration();
AddCpuConfiguration();
AddMemoryConfiguration();
// Storage configuration
AddStorageConfiguration();
// Network configuration
AddNetworkConfiguration();
// Display configuration
AddDisplayConfiguration();
// Boot configuration
AddBootConfiguration();
// Advanced features
AddAdvancedConfiguration();
// Extra arguments
_arguments.AddRange(_config.Advanced.ExtraArgs);
// Handle CPU model based on virtualization type
if (_virtualizationType == VirtualizationType.TCG || _virtualizationType == VirtualizationType.HyperV)
{
// Replace 'host' CPU model with 'qemu64' for TCG compatibility
for (int i = 0; i < _arguments.Count; i++)
{
if (_arguments[i] == "-cpu" && i + 1 < _arguments.Count && _arguments[i + 1] == "host")
{
_arguments[i + 1] = "qemu64";
}
}
}
// Remove -enable-kvm if not using KVM
if (_virtualizationType != VirtualizationType.KVM)
{
_arguments.RemoveAll(arg => arg == "-enable-kvm");
}
// Add WHPX-specific configurations
if (_virtualizationType == VirtualizationType.HyperV)
{
AddWHPXSpecificConfiguration();
}
return string.Join(" ", _arguments);
}
private void AddMachineConfiguration()
{
_arguments.Add("-machine");
switch (_virtualizationType)
{
case VirtualizationType.KVM:
_arguments.Add("type=q35,accel=kvm:tcg");
break;
case VirtualizationType.HyperV:
// Use WHPX hardware acceleration
_arguments.Add("type=pc-i440fx-10.1,accel=whpx:tcg,kernel-irqchip=off");
break;
case VirtualizationType.HAXM:
_arguments.Add("type=q35,accel=hax:tcg");
break;
case VirtualizationType.HVF:
_arguments.Add("type=q35,accel=hvf:tcg");
break;
case VirtualizationType.TCG:
default:
_arguments.Add("type=q35,accel=tcg");
break;
}
}
private void AddCpuConfiguration()
{
var cpu = _config.Cpu;
_arguments.Add("-cpu");
_arguments.Add(cpu.Model);
// For WHPX, use simpler CPU configuration to avoid exit code 4
if (_virtualizationType == VirtualizationType.HyperV)
{
_arguments.Add("-smp");
_arguments.Add($"{Math.Min(cpu.Cores, 2)},cores={Math.Min(cpu.Cores, 2)},sockets=1,threads=1");
}
else
{
_arguments.Add("-smp");
_arguments.Add($"{cpu.Cores},cores={cpu.Cores},sockets={cpu.Sockets},threads={cpu.Threads}");
}
}
private void AddMemoryConfiguration()
{
var memory = _config.Memory;
// For WHPX, use smaller memory allocation to avoid exit code 4
if (_virtualizationType == VirtualizationType.HyperV)
{
var whpxMemorySize = Math.Min(memory.Size, 4096); // Limit to 4GB for WHPX
_arguments.Add("-m");
_arguments.Add($"{whpxMemorySize}{memory.Unit}");
}
else
{
_arguments.Add("-m");
_arguments.Add($"{memory.Size}{memory.Unit}");
}
}
private void AddStorageConfiguration()
{
var storage = _config.Storage;
// Add disks
for (int i = 0; i < storage.Disks.Count; i++)
{
var disk = storage.Disks[i];
var driveLetter = (char)('a' + i);
_arguments.Add("-drive");
var driveArgs = $"file={disk.Path.Replace('\\', '/')},format={disk.Format},cache={disk.Cache},id=drive{i},if=none";
_arguments.Add(driveArgs);
// For WHPX, only use the first disk on IDE to avoid conflicts
if (_virtualizationType == VirtualizationType.HyperV && i > 0)
{
Console.WriteLine($"Warning: Additional disk {i} disabled for WHPX compatibility to avoid IDE conflicts. Disk: {disk.Path}");
continue;
}
// Add device
_arguments.Add("-device");
if (disk.Interface == "virtio" && _virtualizationType != VirtualizationType.HyperV)
{
// Use virtio for better performance, but avoid with WHPX due to MSI issues
_arguments.Add($"virtio-blk-pci,drive=drive{i}");
}
else
{
// Use IDE for WHPX compatibility
_arguments.Add($"ide-hd,drive=drive{i}");
}
}
// Add CD-ROM if specified
if (!string.IsNullOrEmpty(storage.Cdrom))
{
if (_virtualizationType == VirtualizationType.HyperV)
{
// For WHPX, use the second IDE controller for CD-ROM
_arguments.Add("-drive");
_arguments.Add($"file={storage.Cdrom.Replace('\\', '/')},media=cdrom,if=ide,index=1");
}
else
{
// Use standard CD-ROM for other virtualization types
_arguments.Add("-cdrom");
_arguments.Add(storage.Cdrom.Replace('\\', '/'));
}
}
}
private void AddNetworkConfiguration()
{
var network = _config.Network;
for (int i = 0; i < network.Interfaces.Count; i++)
{
var nic = network.Interfaces[i];
// Use user network as fallback since bridge might not be available
_arguments.Add("-netdev");
var netdevArgs = $"user,id=net{i}";
_arguments.Add(netdevArgs);
_arguments.Add("-device");
var deviceArgs = "";
// Use different network models based on virtualization type
if (nic.Model == "virtio-net-pci" && _virtualizationType == VirtualizationType.HyperV)
{
// Use e1000 for WHPX compatibility to avoid MSI issues
deviceArgs = $"e1000,netdev=net{i}";
}
else
{
deviceArgs = $"{nic.Model},netdev=net{i}";
}
if (!string.IsNullOrEmpty(nic.Mac))
{
deviceArgs += $",mac={nic.Mac}";
}
_arguments.Add(deviceArgs);
}
}
private void AddDisplayConfiguration()
{
var display = _config.Display;
_arguments.Add("-display");
_arguments.Add(display.Type);
_arguments.Add("-vga");
_arguments.Add(display.Vga);
if (display.EnableSpice)
{
_arguments.Add("-spice");
_arguments.Add($"port={display.SpicePort},addr=127.0.0.1,disable-ticketing=on");
}
}
private void AddBootConfiguration()
{
var boot = _config.Boot;
if (boot.Order.Count > 0)
{
_arguments.Add("-boot");
_arguments.Add($"order={string.Join("", boot.Order)}");
}
if (!string.IsNullOrEmpty(boot.Kernel))
{
_arguments.Add("-kernel");
_arguments.Add(boot.Kernel);
}
if (!string.IsNullOrEmpty(boot.Initrd))
{
_arguments.Add("-initrd");
_arguments.Add(boot.Initrd);
}
if (!string.IsNullOrEmpty(boot.Cmdline))
{
_arguments.Add("-append");
_arguments.Add(boot.Cmdline);
}
}
private void AddAdvancedConfiguration()
{
var advanced = _config.Advanced;
if (advanced.EnableAudio)
{
_arguments.Add("-device");
_arguments.Add("intel-hda");
_arguments.Add("-device");
_arguments.Add("hda-duplex");
}
if (advanced.EnableUsb)
{
_arguments.Add("-usb");
_arguments.Add("-device");
_arguments.Add("usb-tablet");
}
// Disable virtio devices for WHPX to avoid MSI issues
if (advanced.EnableBalloon && _virtualizationType != VirtualizationType.HyperV)
{
_arguments.Add("-device");
_arguments.Add("virtio-balloon-pci");
}
if (advanced.EnableVirtioRng && _virtualizationType != VirtualizationType.HyperV)
{
_arguments.Add("-device");
_arguments.Add("virtio-rng-pci");
}
if (advanced.EnableVirtioFs && _virtualizationType != VirtualizationType.HyperV)
{
_arguments.Add("-device");
_arguments.Add("virtio-fs-pci");
}
// Add shared folders (disabled for compatibility)
// Note: 9p filesystem support is not available in all QEMU builds
// For now, shared folders are disabled to ensure compatibility
if (advanced.SharedFolders.Any())
{
// Log that shared folders are disabled
Console.WriteLine($"Warning: Shared folders are disabled for compatibility. {advanced.SharedFolders.Count} folder(s) configured but not used.");
}
}
private void AddWHPXSpecificConfiguration()
{
// Add WHPX-specific configurations to avoid MSI issues
// Disable MSI for better WHPX compatibility
_arguments.Add("-global");
_arguments.Add("pcie-root-port.msi=off");
// Use legacy interrupt mode for better compatibility
_arguments.Add("-global");
_arguments.Add("pcie-root-port.msix=off");
// Add additional WHPX optimizations
_arguments.Add("-global");
_arguments.Add("pcie-root-port.ari=off");
// Add WHPX-specific optimizations
_arguments.Add("-rtc");
_arguments.Add("base=localtime");
// Memory allocation optimizations for WHPX
// Note: -mem-path is not needed for WHPX and can cause issues
// Memory preallocation is not available in this QEMU build
// Add additional WHPX optimizations to avoid exit code 4
_arguments.Add("-no-reboot");
_arguments.Add("-no-shutdown");
// Use simpler interrupt handling (removed KVM-specific option)
// Disable some features that might cause issues with WHPX
// _arguments.Add("-no-acpi"); // Commented out as it might cause issues
}
}

View File

@@ -0,0 +1,927 @@
using System.Diagnostics;
using System.Threading;
using QemuVmManager.Models;
namespace QemuVmManager.Core;
public class QemuProcessManager
{
private readonly Dictionary<string, Process> _runningVms = new();
private readonly Dictionary<string, VmStatus> _vmStatuses = new();
private readonly Dictionary<string, PerformanceMonitor> _performanceMonitors = new();
public event EventHandler<VmStatusChangedEventArgs>? VmStatusChanged;
public bool IsQemuInstalled()
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-system-x86_64",
Arguments = "--version",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
var started = process.Start();
if (!started)
{
return false;
}
process.WaitForExit(5000);
return process.ExitCode == 0;
}
catch
{
return false;
}
}
public string GetQemuVersion()
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-system-x86_64",
Arguments = "--version",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
var started = process.Start();
if (!started)
{
return "Unknown";
}
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit(5000);
if (process.ExitCode == 0)
{
var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
return lines.FirstOrDefault() ?? "Unknown";
}
return "Unknown";
}
catch
{
return "Unknown";
}
}
public string GetQemuAccelerators()
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-system-x86_64",
Arguments = "-accel help",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
var started = process.Start();
if (!started)
{
return "Unknown";
}
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit(5000);
if (process.ExitCode == 0)
{
return output.Trim();
}
return "Unknown";
}
catch
{
return "Unknown";
}
}
public VirtualizationType GetAvailableVirtualization()
{
try
{
// First check if virtualization is enabled in BIOS
if (!IsVirtualizationEnabled())
{
return VirtualizationType.TCG;
}
if (OperatingSystem.IsLinux())
{
// Check for KVM support on Linux
if (File.Exists("/dev/kvm"))
{
// Test if KVM is accessible
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-system-x86_64",
Arguments = "-accel help",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (process.Start())
{
process.WaitForExit(3000);
var output = process.StandardOutput.ReadToEnd();
if (output.Contains("kvm") && process.ExitCode == 0)
{
return VirtualizationType.KVM;
}
}
}
return VirtualizationType.TCG;
}
else if (OperatingSystem.IsWindows())
{
// Check QEMU's available accelerators first
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-system-x86_64",
Arguments = "-accel help",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (process.Start())
{
process.WaitForExit(3000);
var output = process.StandardOutput.ReadToEnd();
// Check for WHPX (Windows Hypervisor Platform) support
if (output.Contains("whpx") && process.ExitCode == 0)
{
return VirtualizationType.HyperV;
}
// Check for Hyper-V support (hvf)
if (output.Contains("hvf") && process.ExitCode == 0)
{
return VirtualizationType.HVF;
}
// Check for HAXM support
if (output.Contains("hax") && process.ExitCode == 0)
{
return VirtualizationType.HAXM;
}
}
// Fallback: Check for Hyper-V support using WMI
try
{
var wmiProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
Arguments = "-Command \"Get-WmiObject -Class Msvm_VirtualSystemSettingData -Namespace root\\virtualization\\v2 -ErrorAction SilentlyContinue | Select-Object -First 1\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (wmiProcess.Start())
{
wmiProcess.WaitForExit(3000);
if (wmiProcess.ExitCode == 0 && !string.IsNullOrEmpty(wmiProcess.StandardOutput.ReadToEnd()))
{
return VirtualizationType.HyperV;
}
}
}
catch
{
// Hyper-V check failed
}
return VirtualizationType.TCG;
}
else if (OperatingSystem.IsMacOS())
{
// Check for HVF (Hypervisor.framework) on macOS
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-system-x86_64",
Arguments = "-accel help",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (process.Start())
{
process.WaitForExit(3000);
var output = process.StandardOutput.ReadToEnd();
if (output.Contains("hvf") && process.ExitCode == 0)
{
return VirtualizationType.HVF;
}
}
return VirtualizationType.TCG;
}
return VirtualizationType.TCG;
}
catch
{
return VirtualizationType.TCG;
}
}
public bool IsVirtualizationEnabled()
{
try
{
if (OperatingSystem.IsLinux())
{
// Check if virtualization is enabled in BIOS
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "lscpu",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (process.Start())
{
process.WaitForExit(3000);
var output = process.StandardOutput.ReadToEnd();
return output.Contains("Virtualization:") &&
(output.Contains("VT-x") || output.Contains("AMD-V") || output.Contains("SVM"));
}
}
else if (OperatingSystem.IsWindows())
{
// Multiple methods to check virtualization on Windows
// Method 1: Check using systeminfo
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "systeminfo",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (process.Start())
{
process.WaitForExit(3000);
var output = process.StandardOutput.ReadToEnd();
// Check multiple possible virtualization indicators
if (output.Contains("Virtualization Enabled In Firmware: Yes") ||
output.Contains("Virtualization: Enabled") ||
output.Contains("Hyper-V Requirements:") && output.Contains("Yes"))
{
return true;
}
}
}
catch
{
// Continue to next method
}
// Method 2: Check using PowerShell Get-ComputerInfo
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
Arguments = "-Command \"Get-ComputerInfo | Select-Object HyperVRequirementVirtualizationFirmwareEnabled\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (process.Start())
{
process.WaitForExit(3000);
var output = process.StandardOutput.ReadToEnd();
return output.Contains("True");
}
}
catch
{
// Continue to next method
}
// Method 3: Check using wmic
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "wmic",
Arguments = "cpu get VirtualizationFirmwareEnabled",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (process.Start())
{
process.WaitForExit(3000);
var output = process.StandardOutput.ReadToEnd();
return output.Contains("TRUE");
}
}
catch
{
// Continue to next method
}
// Method 4: Check using PowerShell Get-WmiObject
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
Arguments = "-Command \"Get-WmiObject -Class Msvm_VirtualSystemSettingData -Namespace root\\virtualization\\v2 | Select-Object VirtualSystemIdentifiers\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (process.Start())
{
process.WaitForExit(3000);
var output = process.StandardOutput.ReadToEnd();
// If we can query Hyper-V WMI, virtualization is likely enabled
return !output.Contains("Get-WmiObject") && output.Length > 0;
}
}
catch
{
// All methods failed
}
}
return false;
}
catch
{
return false;
}
}
public async Task<bool> StartVmAsync(VmConfiguration config)
{
try
{
if (_runningVms.ContainsKey(config.Name))
{
throw new InvalidOperationException($"VM '{config.Name}' is already running");
}
var virtualizationType = GetAvailableVirtualization();
var commandBuilder = new QemuCommandBuilder(config, virtualizationType);
var command = commandBuilder.BuildCommand();
Console.WriteLine($"[{config.Name}] Starting QEMU with command:");
Console.WriteLine($"[{config.Name}] {command}");
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "qemu-system-x86_64",
Arguments = command.Replace("qemu-system-x86_64 ", ""),
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = false
},
EnableRaisingEvents = true
};
var outputLines = new List<string>();
var errorLines = new List<string>();
// Set up event handlers for output capture
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
outputLines.Add(e.Data);
Console.WriteLine($"[{config.Name}] {e.Data}");
}
};
process.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
errorLines.Add(e.Data);
Console.WriteLine($"[{config.Name}] ERROR: {e.Data}");
}
};
process.Exited += (sender, e) => OnVmExitedWithDetails(config.Name, process, outputLines, errorLines);
var started = process.Start();
if (!started)
{
throw new InvalidOperationException("Failed to start QEMU process");
}
// Start reading output
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// Wait a moment to see if it starts successfully
await Task.Delay(3000);
if (process.HasExited)
{
var errorMessage = $"QEMU process exited immediately with code {process.ExitCode}";
if (errorLines.Any())
{
errorMessage += $". Errors: {string.Join("; ", errorLines)}";
}
if (outputLines.Any())
{
errorMessage += $". Output: {string.Join("; ", outputLines)}";
}
Console.WriteLine($"[{config.Name}] {errorMessage}");
UpdateVmStatus(config.Name, VmState.Error, -1, errorMessage);
throw new InvalidOperationException(errorMessage);
}
_runningVms[config.Name] = process;
UpdateVmStatus(config.Name, VmState.Running, process.Id);
// Start monitoring in background
_ = Task.Run(() => MonitorVmAsync(config.Name, process));
return true;
}
catch (Exception ex)
{
UpdateVmStatus(config.Name, VmState.Error, -1, ex.Message);
throw;
}
}
public async Task<bool> StopVmAsync(string vmName, bool force = false)
{
if (!_runningVms.TryGetValue(vmName, out var process))
{
return false;
}
try
{
UpdateVmStatus(vmName, VmState.Stopping, process.Id);
if (force)
{
process.Kill();
}
else
{
// Try graceful shutdown first
process.CloseMainWindow();
// Wait for graceful shutdown
try
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await process.WaitForExitAsync(cts.Token);
}
catch (OperationCanceledException)
{
// Timeout occurred, kill the process
process.Kill();
}
}
return true;
}
catch (Exception ex)
{
UpdateVmStatus(vmName, VmState.Error, process.Id, ex.Message);
throw;
}
}
public async Task<bool> PauseVmAsync(string vmName)
{
if (!_runningVms.TryGetValue(vmName, out var process))
{
return false;
}
try
{
// Use QEMU monitor to pause VM
await SendQemuCommandAsync(vmName, "stop");
UpdateVmStatus(vmName, VmState.Paused, process.Id);
return true;
}
catch (Exception ex)
{
UpdateVmStatus(vmName, VmState.Error, process.Id, ex.Message);
throw;
}
}
public async Task<bool> ResumeVmAsync(string vmName)
{
if (!_runningVms.TryGetValue(vmName, out var process))
{
return false;
}
try
{
// Use QEMU monitor to resume VM
await SendQemuCommandAsync(vmName, "cont");
UpdateVmStatus(vmName, VmState.Running, process.Id);
return true;
}
catch (Exception ex)
{
UpdateVmStatus(vmName, VmState.Error, process.Id, ex.Message);
throw;
}
}
public VmStatus? GetVmStatus(string vmName)
{
return _vmStatuses.TryGetValue(vmName, out var status) ? status : null;
}
public IEnumerable<VmStatus> GetAllVmStatuses()
{
return _vmStatuses.Values;
}
public bool IsVmRunning(string vmName)
{
return _runningVms.ContainsKey(vmName) && !_runningVms[vmName].HasExited;
}
private async Task MonitorVmAsync(string vmName, Process process)
{
try
{
while (!process.HasExited)
{
await Task.Delay(5000); // Check every 5 seconds
// Update resource usage if VM is running
if (process.HasExited == false)
{
var resourceUsage = await GetVmResourceUsageAsync(vmName);
UpdateVmResourceUsage(vmName, resourceUsage);
}
}
}
catch (Exception ex)
{
UpdateVmStatus(vmName, VmState.Error, process.Id, ex.Message);
}
}
private Task<VmResourceUsage> GetVmResourceUsageAsync(string vmName)
{
try
{
// This is a simplified implementation
// In a real scenario, you would use QEMU monitor commands or libvirt
var usage = new VmResourceUsage();
// Get CPU usage from process
if (_runningVms.TryGetValue(vmName, out var process))
{
var startTime = process.StartTime;
var totalProcessorTime = process.TotalProcessorTime;
var realTime = DateTime.Now - startTime;
usage.CpuUsage = (totalProcessorTime.TotalMilliseconds / (Environment.ProcessorCount * realTime.TotalMilliseconds)) * 100;
// Get memory usage
usage.MemoryUsage = process.WorkingSet64 / (1024 * 1024); // Convert to MB
}
return Task.FromResult(usage);
}
catch
{
return Task.FromResult(new VmResourceUsage());
}
}
private async Task SendQemuCommandAsync(string vmName, string command)
{
// This would require QEMU monitor socket or similar mechanism
// For now, this is a placeholder
await Task.Delay(100);
}
private void OnVmExited(string vmName)
{
if (_runningVms.TryGetValue(vmName, out var process))
{
_runningVms.Remove(vmName);
UpdateVmStatus(vmName, VmState.Stopped, -1);
}
}
private void OnVmExitedWithDetails(string vmName, Process process, List<string> outputLines, List<string> errorLines)
{
if (_runningVms.TryGetValue(vmName, out var runningProcess))
{
_runningVms.Remove(vmName);
}
var errorMessage = $"Process exited with code {process.ExitCode}";
if (errorLines.Any())
{
errorMessage += $". Errors: {string.Join("; ", errorLines)}";
}
if (outputLines.Any())
{
errorMessage += $". Output: {string.Join("; ", outputLines)}";
}
Console.WriteLine($"[{vmName}] {errorMessage}");
UpdateVmStatus(vmName, VmState.Stopped, -1, errorMessage);
}
private void UpdateVmStatus(string vmName, VmState state, int processId, string? errorMessage = null)
{
if (!_vmStatuses.TryGetValue(vmName, out var status))
{
status = new VmStatus { Name = vmName };
_vmStatuses[vmName] = status;
}
var oldState = status.State;
status.State = state;
status.ProcessId = processId;
status.ErrorMessage = errorMessage;
switch (state)
{
case VmState.Running:
status.StartedAt = DateTime.UtcNow;
status.StoppedAt = null;
break;
case VmState.Stopped:
case VmState.Error:
status.StoppedAt = DateTime.UtcNow;
break;
}
VmStatusChanged?.Invoke(this, new VmStatusChangedEventArgs(vmName, oldState, state));
}
private void UpdateVmResourceUsage(string vmName, VmResourceUsage usage)
{
if (_vmStatuses.TryGetValue(vmName, out var status))
{
status.ResourceUsage = usage;
}
}
// Enhanced performance monitoring methods
public async Task<VmPerformanceMetrics> GetVmPerformanceMetricsAsync(string vmName)
{
if (!_performanceMonitors.TryGetValue(vmName, out var monitor))
{
return new VmPerformanceMetrics();
}
return await monitor.GetCurrentMetricsAsync();
}
public Task StartPerformanceMonitoringAsync(string vmName)
{
if (_runningVms.TryGetValue(vmName, out var process))
{
var monitor = new PerformanceMonitor(process);
_performanceMonitors[vmName] = monitor;
return monitor.StartMonitoringAsync();
}
return Task.CompletedTask;
}
public void StopPerformanceMonitoring(string vmName)
{
if (_performanceMonitors.TryGetValue(vmName, out var monitor))
{
monitor.StopMonitoring();
_performanceMonitors.Remove(vmName);
}
}
public async Task<List<VmPerformanceMetrics>> GetPerformanceHistoryAsync(string vmName, int maxSamples = 100)
{
if (_performanceMonitors.TryGetValue(vmName, out var monitor))
{
return await monitor.GetPerformanceHistoryAsync(maxSamples);
}
return new List<VmPerformanceMetrics>();
}
}
public class PerformanceMonitor
{
private readonly Process _process;
private readonly List<VmPerformanceMetrics> _history = new();
private readonly object _lock = new();
private bool _isMonitoring = false;
private CancellationTokenSource? _cancellationTokenSource;
public PerformanceMonitor(Process process)
{
_process = process;
}
public async Task StartMonitoringAsync()
{
if (_isMonitoring) return;
_isMonitoring = true;
_cancellationTokenSource = new CancellationTokenSource();
_ = Task.Run(async () =>
{
while (_isMonitoring && !_process.HasExited)
{
try
{
var metrics = await GetCurrentMetricsAsync();
lock (_lock)
{
_history.Add(metrics);
// Keep only last 1000 samples
if (_history.Count > 1000)
{
_history.RemoveAt(0);
}
}
await Task.Delay(2000, _cancellationTokenSource.Token); // Sample every 2 seconds
}
catch (OperationCanceledException)
{
break;
}
catch
{
// Continue monitoring even if there's an error
}
}
}, _cancellationTokenSource.Token);
}
public void StopMonitoring()
{
_isMonitoring = false;
_cancellationTokenSource?.Cancel();
}
public Task<VmPerformanceMetrics> GetCurrentMetricsAsync()
{
try
{
var metrics = new VmPerformanceMetrics
{
Timestamp = DateTime.UtcNow,
ProcessId = _process.Id
};
if (!_process.HasExited)
{
_process.Refresh();
// CPU metrics
metrics.CpuUsagePercent = _process.TotalProcessorTime.TotalMilliseconds /
(Environment.ProcessorCount * (DateTime.Now - _process.StartTime).TotalMilliseconds) * 100;
// Memory metrics
metrics.MemoryUsageMB = _process.WorkingSet64 / (1024 * 1024);
metrics.PrivateMemoryMB = _process.PrivateMemorySize64 / (1024 * 1024);
metrics.VirtualMemoryMB = _process.VirtualMemorySize64 / (1024 * 1024);
// Process metrics
metrics.ThreadCount = _process.Threads.Count;
metrics.HandleCount = _process.HandleCount;
// Performance counters (if available)
try
{
using var cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
metrics.SystemCpuUsagePercent = cpuCounter.NextValue();
}
catch
{
metrics.SystemCpuUsagePercent = 0;
}
}
return Task.FromResult(metrics);
}
catch
{
return Task.FromResult(new VmPerformanceMetrics { Timestamp = DateTime.UtcNow });
}
}
public Task<List<VmPerformanceMetrics>> GetPerformanceHistoryAsync(int maxSamples = 100)
{
lock (_lock)
{
return Task.FromResult(_history.TakeLast(maxSamples).ToList());
}
}
}
public class VmStatusChangedEventArgs : EventArgs
{
public string VmName { get; }
public VmState OldState { get; }
public VmState NewState { get; }
public VmStatusChangedEventArgs(string vmName, VmState oldState, VmState newState)
{
VmName = vmName;
OldState = oldState;
NewState = newState;
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\QemuVmManager.Models\QemuVmManager.Models.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
namespace QemuVmManager.Models;
public enum VirtualizationType
{
/// <summary>
/// Kernel-based Virtual Machine (Linux)
/// </summary>
KVM,
/// <summary>
/// Hyper-V (Windows)
/// </summary>
HyperV,
/// <summary>
/// Intel Hardware Accelerated Execution Manager (Windows/macOS)
/// </summary>
HAXM,
/// <summary>
/// Hypervisor.framework (macOS)
/// </summary>
HVF,
/// <summary>
/// Tiny Code Generator (Software emulation)
/// </summary>
TCG
}

View File

@@ -0,0 +1,189 @@
using System.Text.Json.Serialization;
namespace QemuVmManager.Models;
public class VmConfiguration
{
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
[JsonPropertyName("description")]
public string Description { get; set; } = string.Empty;
[JsonPropertyName("cpu")]
public CpuConfiguration Cpu { get; set; } = new();
[JsonPropertyName("memory")]
public MemoryConfiguration Memory { get; set; } = new();
[JsonPropertyName("storage")]
public StorageConfiguration Storage { get; set; } = new();
[JsonPropertyName("network")]
public NetworkConfiguration Network { get; set; } = new();
[JsonPropertyName("display")]
public DisplayConfiguration Display { get; set; } = new();
[JsonPropertyName("boot")]
public BootConfiguration Boot { get; set; } = new();
[JsonPropertyName("advanced")]
public AdvancedConfiguration Advanced { get; set; } = new();
[JsonPropertyName("created")]
public DateTime Created { get; set; } = DateTime.UtcNow;
[JsonPropertyName("lastModified")]
public DateTime LastModified { get; set; } = DateTime.UtcNow;
}
public class CpuConfiguration
{
[JsonPropertyName("cores")]
public int Cores { get; set; } = 2;
[JsonPropertyName("sockets")]
public int Sockets { get; set; } = 1;
[JsonPropertyName("threads")]
public int Threads { get; set; } = 1;
[JsonPropertyName("model")]
public string Model { get; set; } = "qemu64";
[JsonPropertyName("enableKvm")]
public bool EnableKvm { get; set; } = true;
}
public class MemoryConfiguration
{
[JsonPropertyName("size")]
public long Size { get; set; } = 2048; // MB
[JsonPropertyName("unit")]
public string Unit { get; set; } = "M";
}
public class StorageConfiguration
{
[JsonPropertyName("disks")]
public List<DiskConfiguration> Disks { get; set; } = new();
[JsonPropertyName("cdrom")]
public string? Cdrom { get; set; }
}
public class DiskConfiguration
{
[JsonPropertyName("path")]
public string Path { get; set; } = string.Empty;
[JsonPropertyName("size")]
public long Size { get; set; } = 10; // GB
[JsonPropertyName("format")]
public string Format { get; set; } = "qcow2";
[JsonPropertyName("interface")]
public string Interface { get; set; } = "virtio";
[JsonPropertyName("cache")]
public string Cache { get; set; } = "writeback";
[JsonPropertyName("isBoot")]
public bool IsBoot { get; set; } = false;
}
public class NetworkConfiguration
{
[JsonPropertyName("interfaces")]
public List<NetworkInterfaceConfiguration> Interfaces { get; set; } = new();
[JsonPropertyName("bridge")]
public string Bridge { get; set; } = "virbr0";
}
public class NetworkInterfaceConfiguration
{
[JsonPropertyName("type")]
public string Type { get; set; } = "bridge";
[JsonPropertyName("model")]
public string Model { get; set; } = "virtio-net-pci";
[JsonPropertyName("mac")]
public string? Mac { get; set; }
[JsonPropertyName("bridge")]
public string Bridge { get; set; } = "virbr0";
}
public class DisplayConfiguration
{
[JsonPropertyName("type")]
public string Type { get; set; } = "gtk";
[JsonPropertyName("vga")]
public string Vga { get; set; } = "virtio";
[JsonPropertyName("resolution")]
public string Resolution { get; set; } = "1024x768";
[JsonPropertyName("enableSpice")]
public bool EnableSpice { get; set; } = false;
[JsonPropertyName("spicePort")]
public int SpicePort { get; set; } = 5930;
}
public class BootConfiguration
{
[JsonPropertyName("order")]
public List<string> Order { get; set; } = new() { "c", "d", "n" };
[JsonPropertyName("kernel")]
public string? Kernel { get; set; }
[JsonPropertyName("initrd")]
public string? Initrd { get; set; }
[JsonPropertyName("cmdline")]
public string? Cmdline { get; set; }
}
public class AdvancedConfiguration
{
[JsonPropertyName("enableAudio")]
public bool EnableAudio { get; set; } = false;
[JsonPropertyName("enableUsb")]
public bool EnableUsb { get; set; } = false;
[JsonPropertyName("enableBalloon")]
public bool EnableBalloon { get; set; } = true;
[JsonPropertyName("enableVirtioRng")]
public bool EnableVirtioRng { get; set; } = true;
[JsonPropertyName("enableVirtioFs")]
public bool EnableVirtioFs { get; set; } = false;
[JsonPropertyName("sharedFolders")]
public List<SharedFolderConfiguration> SharedFolders { get; set; } = new();
[JsonPropertyName("extraArgs")]
public List<string> ExtraArgs { get; set; } = new();
}
public class SharedFolderConfiguration
{
[JsonPropertyName("hostPath")]
public string HostPath { get; set; } = string.Empty;
[JsonPropertyName("guestPath")]
public string GuestPath { get; set; } = string.Empty;
[JsonPropertyName("readOnly")]
public bool ReadOnly { get; set; } = false;
}

View File

@@ -0,0 +1,44 @@
namespace QemuVmManager.Models;
public enum VmState
{
Stopped,
Starting,
Running,
Paused,
Stopping,
Error
}
public class VmStatus
{
public string Name { get; set; } = string.Empty;
public VmState State { get; set; } = VmState.Stopped;
public int ProcessId { get; set; } = -1;
public DateTime? StartedAt { get; set; }
public DateTime? StoppedAt { get; set; }
public string? ErrorMessage { get; set; }
public VmResourceUsage? ResourceUsage { get; set; }
}
public class VmResourceUsage
{
public double CpuUsage { get; set; } = 0.0; // Percentage
public long MemoryUsage { get; set; } = 0; // MB
public long DiskUsage { get; set; } = 0; // MB
public long NetworkRx { get; set; } = 0; // Bytes
public long NetworkTx { get; set; } = 0; // Bytes
}
public class VmPerformanceMetrics
{
public DateTime Timestamp { get; set; }
public int ProcessId { get; set; }
public double CpuUsagePercent { get; set; }
public double SystemCpuUsagePercent { get; set; }
public long MemoryUsageMB { get; set; }
public long PrivateMemoryMB { get; set; }
public long VirtualMemoryMB { get; set; }
public int ThreadCount { get; set; }
public int HandleCount { get; set; }
}

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

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

39
QemuVmManager.sln Normal file
View File

@@ -0,0 +1,39 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QemuVmManager.Console", "QemuVmManager.Console\QemuVmManager.Console.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QemuVmManager.Core", "QemuVmManager.Core\QemuVmManager.Core.csproj", "{B2C3D4E5-F6G7-8901-BCDE-F23456789012}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QemuVmManager.Models", "QemuVmManager.Models\QemuVmManager.Models.csproj", "{C3D4E5F6-G7H8-9012-CDEF-345678901234}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QemuVmManager.Services", "QemuVmManager.Services\QemuVmManager.Services.csproj", "{D4E5F6G7-H8I9-0123-DEF0-456789012345}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
{B2C3D4E5-F6G7-8901-BCDE-F23456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2C3D4E5-F6G7-8901-BCDE-F23456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2C3D4E5-F6G7-8901-BCDE-F23456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2C3D4E5-F6G7-8901-BCDE-F23456789012}.Release|Any CPU.Build.0 = Release|Any CPU
{C3D4E5F6-G7H8-9012-CDEF-345678901234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3D4E5F6-G7H8-9012-CDEF-345678901234}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3D4E5F6-G7H8-9012-CDEF-345678901234}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3D4E5F6-G7H8-9012-CDEF-345678901234}.Release|Any CPU.Build.0 = Release|Any CPU
{D4E5F6G7-H8I9-0123-DEF0-456789012345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4E5F6G7-H8I9-0123-DEF0-456789012345}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4E5F6G7-H8I9-0123-DEF0-456789012345}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4E5F6G7-H8I9-0123-DEF0-456789012345}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

308
README.md Normal file
View File

@@ -0,0 +1,308 @@
# QEMU VM Manager
A comprehensive .NET application for managing QEMU virtual machines with an intuitive console interface.
## Features
- **VM Lifecycle Management**: Create, start, stop, pause, resume, and delete VMs
- **Configuration Management**: Store and manage VM configurations in JSON format
- **VM Cloning**: Clone existing VMs with automatic disk path management
- **Import/Export**: Export and import VM configurations
- **Status Monitoring**: Real-time VM status and resource usage tracking
- **Interactive Console**: User-friendly command-line interface
- **Cross-Platform**: Works on Windows, Linux, and macOS
## Prerequisites
- .NET 8.0 SDK or Runtime
- QEMU installed and available in PATH
- For KVM acceleration: KVM support (Linux) or Hyper-V (Windows)
## Installation
1. **Clone the repository**:
```bash
git clone <repository-url>
cd skystack
```
2. **Build the solution**:
```bash
dotnet build
```
3. **Run the application**:
```bash
dotnet run --project QemuVmManager.Console
```
## Usage
### Starting the Application
```bash
dotnet run --project QemuVmManager.Console
```
You'll see the interactive prompt:
```
=== QEMU VM Manager ===
Type 'help' for available commands
qemu-vm>
```
### Available Commands
#### `help`
Display all available commands and their usage.
#### `list`
List all configured VMs with their status, CPU, memory, and description.
#### `create [name]`
Create a new VM configuration interactively. If no name is provided, you'll be prompted for one.
Example:
```
qemu-vm> create my-vm
Enter VM name: my-vm
Description (optional): My test VM
CPU cores (2): 4
CPU model (qemu64): qemu64
Memory size in MB (2048): 4096
Disk path: /path/to/disk.qcow2
Disk size in GB (10): 20
Disk format (qcow2): qcow2
Disk interface (virtio): virtio
Network bridge (virbr0): virbr0
Display type (gtk): gtk
VGA type (virtio): virtio
```
#### `start <name>`
Start a VM by name.
Example:
```
qemu-vm> start my-vm
```
#### `stop <name> [--force]`
Stop a VM gracefully. Use `--force` for immediate termination.
Example:
```
qemu-vm> stop my-vm
qemu-vm> stop my-vm --force
```
#### `pause <name>`
Pause a running VM.
Example:
```
qemu-vm> pause my-vm
```
#### `resume <name>`
Resume a paused VM.
Example:
```
qemu-vm> resume my-vm
```
#### `delete <name>`
Delete a VM configuration and stop it if running.
Example:
```
qemu-vm> delete my-vm
Are you sure you want to delete VM 'my-vm'? (y/N): y
```
#### `clone <source> <target>`
Clone an existing VM configuration.
Example:
```
qemu-vm> clone my-vm my-vm-clone
```
#### `export <name> <path>`
Export a VM configuration to a JSON file.
Example:
```
qemu-vm> export my-vm /tmp/my-vm-config.json
```
#### `import <path> [name]`
Import a VM configuration from a JSON file.
Example:
```
qemu-vm> import /tmp/my-vm-config.json my-imported-vm
```
#### `status [name]`
Show VM status. Without a name, shows all VMs.
Example:
```
qemu-vm> status
qemu-vm> status my-vm
```
#### `config <name>`
Display detailed VM configuration.
Example:
```
qemu-vm> config my-vm
```
#### `exit` or `quit`
Exit the application.
## Configuration
VM configurations are stored in JSON format in the `vm-configs` directory. Each VM has its own configuration file named `<vm-name>.json`.
### Configuration Structure
```json
{
"name": "my-vm",
"description": "My test VM",
"cpu": {
"cores": 4,
"sockets": 1,
"threads": 1,
"model": "qemu64",
"enableKvm": true
},
"memory": {
"size": 4096,
"unit": "M"
},
"storage": {
"disks": [
{
"path": "/path/to/disk.qcow2",
"size": 20,
"format": "qcow2",
"interface": "virtio",
"cache": "writeback",
"isBoot": true
}
],
"cdrom": null
},
"network": {
"interfaces": [
{
"type": "bridge",
"model": "virtio-net-pci",
"mac": null,
"bridge": "virbr0"
}
],
"bridge": "virbr0"
},
"display": {
"type": "gtk",
"vga": "virtio",
"resolution": "1024x768",
"enableSpice": false,
"spicePort": 5930
},
"boot": {
"order": ["c", "d", "n"],
"kernel": null,
"initrd": null,
"cmdline": null
},
"advanced": {
"enableAudio": false,
"enableUsb": false,
"enableBalloon": true,
"enableVirtioRng": true,
"enableVirtioFs": false,
"sharedFolders": [],
"extraArgs": []
},
"created": "2024-01-01T00:00:00Z",
"lastModified": "2024-01-01T00:00:00Z"
}
```
## Project Structure
```
skystack/
├── QemuVmManager.sln # Solution file
├── QemuVmManager.Models/ # Data models and DTOs
│ ├── VmConfiguration.cs # Main VM configuration model
│ └── VmStatus.cs # VM status and resource usage
├── QemuVmManager.Core/ # Core QEMU operations
│ ├── QemuCommandBuilder.cs # QEMU command generation
│ └── QemuProcessManager.cs # VM process management
├── QemuVmManager.Services/ # High-level services
│ └── VmManagementService.cs # Main VM management service
├── QemuVmManager.Console/ # Console application
│ └── Program.cs # Main program and UI
└── README.md # This file
```
## Architecture
The application follows a layered architecture:
1. **Models Layer** (`QemuVmManager.Models`): Contains data structures for VM configuration and status
2. **Core Layer** (`QemuVmManager.Core`): Handles QEMU command generation and process management
3. **Services Layer** (`QemuVmManager.Services`): Provides high-level VM management operations
4. **Console Layer** (`QemuVmManager.Console`): User interface and command processing
## QEMU Integration
The application generates QEMU command-line arguments based on VM configurations. Key features:
- **KVM Acceleration**: Automatically enables KVM when available
- **VirtIO Support**: Uses VirtIO devices for better performance
- **Network Bridging**: Supports bridge networking
- **SPICE Support**: Optional SPICE remote desktop
- **Shared Folders**: 9P filesystem sharing
- **Advanced Features**: Audio, USB, balloon driver, RNG
## Troubleshooting
### Common Issues
1. **QEMU not found**: Ensure QEMU is installed and in your PATH
2. **Permission denied**: Run with appropriate permissions for KVM/bridge access
3. **Network bridge not found**: Create the bridge interface (e.g., `virbr0`)
4. **Disk file not found**: Ensure disk paths are correct and accessible
### Debug Mode
To see the generated QEMU commands, you can modify the `QemuProcessManager.cs` to log the command before execution.
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Acknowledgments
- QEMU team for the excellent virtualization platform
- .NET community for the robust framework
- Contributors and users of this project

33
build.bat Normal file
View File

@@ -0,0 +1,33 @@
@echo off
echo === QEMU VM Manager Build Script ===
REM Check if .NET is available
dotnet --version >nul 2>&1
if %errorlevel% neq 0 (
echo Error: .NET is not available or not properly installed.
echo Please install .NET 8.0 SDK from: https://dotnet.microsoft.com/download
pause
exit /b 1
)
echo Cleaning previous builds...
dotnet clean
echo Restoring packages...
dotnet restore
echo Building solution...
dotnet build --configuration Release
if %errorlevel% equ 0 (
echo Build completed successfully!
echo.
echo To run the application:
echo dotnet run --project QemuVmManager.Console
) else (
echo Build failed!
pause
exit /b 1
)
pause

39
build.ps1 Normal file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env pwsh
Write-Host "=== QEMU VM Manager Build Script ===" -ForegroundColor Green
# Check if .NET is available
try {
$dotnetVersion = dotnet --version
Write-Host "Using .NET version: $dotnetVersion" -ForegroundColor Yellow
} catch {
Write-Host "Error: .NET is not available or not properly installed." -ForegroundColor Red
Write-Host "Please install .NET 8.0 SDK from: https://dotnet.microsoft.com/download" -ForegroundColor Yellow
exit 1
}
# Clean previous builds
Write-Host "Cleaning previous builds..." -ForegroundColor Blue
dotnet clean
# Restore packages
Write-Host "Restoring packages..." -ForegroundColor Blue
dotnet restore
# Build the solution
Write-Host "Building solution..." -ForegroundColor Blue
dotnet build --configuration Release
if ($LASTEXITCODE -eq 0) {
Write-Host "Build completed successfully!" -ForegroundColor Green
# Show build output
Write-Host "`nBuild output:" -ForegroundColor Yellow
dotnet build --configuration Release --verbosity quiet
Write-Host "`nTo run the application:" -ForegroundColor Cyan
Write-Host "dotnet run --project QemuVmManager.Console" -ForegroundColor White
} else {
Write-Host "Build failed!" -ForegroundColor Red
exit 1
}

View File

@@ -0,0 +1,81 @@
{
"name": "ubuntu-desktop",
"description": "Ubuntu Desktop VM for development",
"cpu": {
"cores": 4,
"sockets": 1,
"threads": 1,
"model": "qemu64",
"enableKvm": true
},
"memory": {
"size": 8192,
"unit": "M"
},
"storage": {
"disks": [
{
"path": "C:\\Users\\mahes\\Disks\\ubuntu-desktop.qcow2",
"size": 50,
"format": "qcow2",
"interface": "virtio",
"cache": "writeback",
"isBoot": true
},
{
"path": "C:\\Users\\mahes\\Disks\\ubuntu-data.qcow2",
"size": 100,
"format": "qcow2",
"interface": "virtio",
"cache": "writeback",
"isBoot": false
}
],
"cdrom": "C:\\Users\\mahes\\Downloads\\ubuntu-24.04.3-desktop-amd64.iso"
},
"network": {
"interfaces": [
{
"type": "bridge",
"model": "virtio-net-pci",
"mac": "52:54:00:12:34:56",
"bridge": "virbr0"
}
],
"bridge": "virbr0"
},
"display": {
"type": "gtk",
"vga": "virtio",
"resolution": "1920x1080",
"enableSpice": true,
"spicePort": 5930
},
"boot": {
"order": ["c", "d", "n"],
"kernel": null,
"initrd": null,
"cmdline": null
},
"advanced": {
"enableAudio": true,
"enableUsb": true,
"enableBalloon": true,
"enableVirtioRng": true,
"enableVirtioFs": false,
"sharedFolders": [
{
"hostPath": "C:\\Users\\mahes\\Shared",
"guestPath": "/mnt/shared",
"readOnly": false
}
],
"extraArgs": [
"-enable-kvm",
"-cpu",
"host"
]
},
"created": "2024-01-01T10:00:00Z",
"lastModified": "2024-01-01T10:00:00Z"
}