VM Operations
Complete guide to VM lifecycle management - create, start, stop, destroy, snapshots using libvirt
VM Operations
Introduction
The VM lifecycle management system manages VM creation, startup, and teardown through libvirt. The CreateMachineService handles storage allocation, firewall configuration, XML generation, and automated OS installation.
Uses libvirt's KVM/QEMU for virtualization, Prisma for database, and automated OS installation. All operations include automatic cleanup on errors.
VM Creation Process
Overview
Creates VMs from database configuration through phases: validation, data fetching, ISO generation, storage setup, firewall configuration, XML generation, and VM startup.
CreateMachineService Architecture
File: backend/app/utils/VirtManager/createMachineService.ts
Core components:
- Singleton libvirt connection:
getLibvirtConnection() - Prisma client: Database operations
- FirewallManager: Network filter management
- Debugger: Logging
Creation Phases
Initialization
Initialize singleton libvirt connection via getLibvirtConnection() and create FirewallManager instance.
Validation
Validate Prisma client exists and check preconditions via validatePrismaClient().
Data Fetching
Fetch template (resources), configuration (graphics/XML), and applications from database:
prisma.machineTemplate.findUnique()prisma.machineConfiguration.findUnique()prisma.machineApplication.findMany()
Unattended ISO Generation
Generate custom ISO with autoinstall configuration via createUnattendedManager() and generateNewImage(). See Unattended Installation.
Status Update
Update machine status to 'building' in database transaction (20s timeout).
Storage Setup
Ensure storage pool exists via ensureStoragePoolExists(), create QCOW2 volume via createStorageVolume(), get path via StorageVol.getPath(). Always use StorageVol.getPath() instead of hardcoding paths.
Firewall Verification
Verify firewall infrastructure exists (created by Prisma callbacks in utils/modelCallbacks/machine.ts). The ensureFirewallForVM() method verifies and creates filters if missing.
XML Generation
Generate libvirt domain XML via xmlGenerator.generate() with memory, CPU, storage, network, graphics, channels, and optional features.
VM Definition and Start
Define VM via VirtualMachine.defineXml() and start via vm.create(). Note: create() starts the domain (confusing name).
Update status to 'running' in database transaction.
Error Handling and Rollback
In CreateMachineService.create() method, on failure, cleanup occurs in reverse order:
- Log error with stack trace
- Delete temporary ISO (if in temp directory)
- Delete storage volume
- Cleanup database via MachineCleanupService
- Re-throw error
Storage Management
Storage Pool Configuration
In ensureStoragePoolExists() function, directory-based storage pools:
- Pool name:
INFINIBAY_STORAGE_POOL_NAME(default: 'default') - Pool path:
INFINIBAY_STORAGE_POOL_PATH(default: '/opt/infinibay/disks') - Type: 'dir'
- Auto-start enabled
Created automatically if missing.
Storage Volume Creation
In createStorageVolume() function, key steps:
- Name:
${machine.internalName}-main.qcow2 - Format: QCOW2 with
lazy_refcounts,extended_l2,nocow - Thin provisioning (allocation: 0)
- Capacity from template or machine.diskSizeGB
- Metadata pre-allocation flag for performance
- Check for name collision
- Create via
StorageVol.createXml() - Refresh pool, get path, verify file exists
Path Resolution
CRITICAL: Use StorageVol.getPath() to get the actual volume path. Never hardcode paths—breaks when storage pool configuration changes. See deprecated setStorage() method warning in xmlGenerator.ts.
Environment Variables
| Variable | Description | Default |
|---|---|---|
INFINIBAY_BASE_DIR |
Base directory for all Infinibay data | '/opt/infinibay' |
INFINIBAY_STORAGE_POOL_NAME |
Custom storage pool name | 'default' |
INFINIBAY_STORAGE_POOL_PATH |
Custom storage pool directory | ${INFINIBAY_BASE_DIR}/disks |
Firewall Integration
Automatic Firewall Setup
Prisma callbacks in backend/app/utils/modelCallbacks/machine.ts create firewall infrastructure on machine insert. CreateMachineService verifies it exists via ensureFirewallForVM(), which checks for filters and creates them if missing.
Filter Application
In CreateMachineService, only VM filter added to domain XML via xmlGenerator.addVMNWFilter(). VM filter inherits from department filter via <filterref> in filter definition. libvirt does NOT support multiple <filterref> per interface.
Filter Priority
Priority 100: Department (evaluated first) Priority 200: VM (can override department rules)
Template System
Templates
Templates in MachineTemplate model provide default cores, RAM, storage. Optional—machines can use custom sizing.
Resource resolution in CreateMachineService uses template values with machine-specific fallback.
VM Definition and Startup
Domain Operations
Define VM via VirtualMachine.defineXml(). On error, capture libvirt lastError() and log XML.
Start VM via vm.create() (confusing name—starts already-defined domain). On failure: check host resources, storage access, network bridge.
VM Lifecycle Operations
These operations control VM state after creation:
| Operation | Method | Description |
|---|---|---|
| Start VM | VirtualMachine.create() |
Starts a defined but inactive domain |
| Stop VM (Graceful) | VirtualMachine.shutdown() |
Sends ACPI shutdown signal to guest OS (like clicking "Shutdown" in Windows) |
| Force Power Off | VirtualMachine.destroy() |
Immediately stops VM (like pulling power plug—may cause data loss) |
| Restart VM (Graceful) | VirtualMachine.reboot() |
Sends ACPI reboot signal to guest OS |
| Reset VM (Hard) | VirtualMachine.reset() |
Hard reset without guest OS cooperation (like pressing reset button) |
| Pause VM | VirtualMachine.suspend() |
Pauses VM execution (keeps in memory, can resume quickly) |
| Resume VM | VirtualMachine.resume() |
Resumes paused VM from exact state |
| Undefine VM | VirtualMachine.undefine() |
Removes VM definition from libvirt (must be stopped first, doesn't delete storage) |
Use graceful operations when possible. Force operations only for unresponsive VMs. Stop before undefining.
Snapshot Operations
Reference: SnapshotService.ts
Types
- Internal: Stored in QCOW2 file (fast, file grows)
- External: New QCOW2 with backing chain (slower, more flexible)
Operations
| Operation | Method | Description |
|---|---|---|
| Create | VirtualMachine.snapshotCreateXml(xml, flags) |
Create from XML |
| List | VirtualMachine.listAllSnapshots(flags) |
List all snapshots |
| Restore | VirtualMachine.revertToSnapshot(snapshot, flags) |
Revert to snapshot |
| Delete | Snapshot.delete(flags) |
Delete snapshot |
GPU Passthrough
PCI address format: 0000:B4:00.0. XMLGenerator.addGPUPassthrough() creates hostdev with managed='yes'.
Requirements:
- IOMMU enabled (Intel:
intel_iommu=on, AMD:amd_iommu=on) - GPU not in use by host
- Allowed vendors: NVIDIA, AMD/ATI
Uses systeminformation library to detect GPUs in getGraphics() resolver. TODO: Filter already-used GPUs.
Unattended Installation System
Generates custom ISOs with autoinstall configs for automated OS installation.
Architecture
UnattendedWindowsManager: Windows 10/11 (unattend.xml)UnattendedUbuntuManager: Ubuntu (cloud-init YAML)UnattendedRedHatManager: Fedora/RedHat (kickstart)
Process
- Validate ISO path
- Generate OS-specific config
- Extract ISO with 7z
- Add config to extracted directory
- Create new ISO with xorriso/mkisofs
- Cleanup temp files
- Return ISO path
Factory pattern in createUnattendedManager() maps OS to manager. Throws error for unsupported OS.
Temporary ISOs: Stored in INFINIBAY_ISO_TEMP_DIR, deleted on rollback. TODO: Delete after successful install.
Configuration Storage
Stored in MachineConfiguration model:
xml: JSON object (not string)graphicProtocol: spice/vncgraphicPassword: Random 8-char passwordgraphicHost: Host addressgraphicPort: -1 (autoport)assignedGpuBus: PCI address if GPU passthrough
Error Handling
Libvirt: Check null returns, capture LibvirtError.lastError().
Transactions: Quick status updates only (20s timeout). Heavy operations outside transactions.
Rollback: Delete temp ISO → storage volume → database cleanup.
Environment Variables Reference
| Variable | Description | Default |
|---|---|---|
INFINIBAY_BASE_DIR |
Base directory for all data | '/opt/infinibay' |
INFINIBAY_STORAGE_POOL_NAME |
Storage pool name | 'default' |
INFINIBAY_STORAGE_POOL_PATH |
Storage pool directory | ${INFINIBAY_BASE_DIR}/disks |
INFINIBAY_ISO_TEMP_DIR |
Temporary ISO directory | ${INFINIBAY_BASE_DIR}/iso/temp |
LIBVIRT_NETWORK_NAME |
Libvirt network name | 'default' |
APP_HOST |
Graphics host address | '0.0.0.0' |
Diagrams
VM Creation Flow
sequenceDiagram
actor User
participant Service as CreateMachineService
participant Unattended as UnattendedManager
participant Storage as StoragePool
participant Firewall as FirewallManager
participant Libvirt
User->>Service: create()
Service->>Service: Initialize & fetch data
Service->>Unattended: generateNewImage()
Unattended-->>Service: ISO path
Service->>Storage: Create QCOW2 volume
Storage-->>Service: Volume path
Service->>Firewall: ensureFirewallForVM()
Service->>Service: Generate XML
Service->>Libvirt: defineXml() + create()
Service-->>User: VM running
Rollback Process
sequenceDiagram
participant Service
participant FileSystem
participant Storage
participant Cleanup as MachineCleanupService
participant DB as Database
Service->>Service: Error detected
Service->>FileSystem: Delete temp ISO
Service->>Storage: Delete volume
Service->>Cleanup: cleanupVM()
Cleanup->>DB: Delete records
Cleanup->>Service: Done
Service->>User: Throw error