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:

  1. Log error with stack trace
  2. Delete temporary ISO (if in temp directory)
  3. Delete storage volume
  4. Cleanup database via MachineCleanupService
  5. 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:

  1. Name: ${machine.internalName}-main.qcow2
  2. Format: QCOW2 with lazy_refcounts, extended_l2, nocow
  3. Thin provisioning (allocation: 0)
  4. Capacity from template or machine.diskSizeGB
  5. Metadata pre-allocation flag for performance
  6. Check for name collision
  7. Create via StorageVol.createXml()
  8. 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

  1. Validate ISO path
  2. Generate OS-specific config
  3. Extract ISO with 7z
  4. Add config to extracted directory
  5. Create new ISO with xorriso/mkisofs
  6. Cleanup temp files
  7. 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/vnc
  • graphicPassword: Random 8-char password
  • graphicHost: Host address
  • graphicPort: -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