import type { IMicroVMConfig, IDriveConfig, INetworkInterfaceConfig, IRateLimiter, } from './interfaces/index.js'; /** * Transforms a camelCase IMicroVMConfig into snake_case Firecracker API payloads. */ export class VMConfig { public config: IMicroVMConfig; constructor(config: IMicroVMConfig) { this.config = config; } /** * Validate the configuration for required fields and constraints. */ public validate(): { valid: boolean; errors: string[] } { const errors: string[] = []; if (!this.config.bootSource) { errors.push('bootSource is required'); } else if (!this.config.bootSource.kernelImagePath) { errors.push('bootSource.kernelImagePath is required'); } if (!this.config.machineConfig) { errors.push('machineConfig is required'); } else { if (!this.config.machineConfig.vcpuCount || this.config.machineConfig.vcpuCount < 1) { errors.push('machineConfig.vcpuCount must be at least 1'); } if (this.config.machineConfig.vcpuCount > 32) { errors.push('machineConfig.vcpuCount must be at most 32'); } if (!this.config.machineConfig.memSizeMib || this.config.machineConfig.memSizeMib < 1) { errors.push('machineConfig.memSizeMib must be at least 1'); } } if (this.config.drives) { const rootDrives = this.config.drives.filter((d) => d.isRootDevice); if (rootDrives.length > 1) { errors.push('Only one root drive is allowed'); } for (const drive of this.config.drives) { if (!drive.driveId) { errors.push('Each drive must have a driveId'); } if (!drive.pathOnHost) { errors.push(`Drive ${drive.driveId}: pathOnHost is required`); } } } if (this.config.vsock) { if (this.config.vsock.guestCid < 3) { errors.push('vsock.guestCid must be >= 3'); } if (!this.config.vsock.udsPath) { errors.push('vsock.udsPath is required'); } } return { valid: errors.length === 0, errors }; } /** * Generate the boot source PUT payload. */ public toBootSourcePayload(): Record { const bs = this.config.bootSource; const payload: Record = { kernel_image_path: bs.kernelImagePath, }; if (bs.bootArgs !== undefined) { payload.boot_args = bs.bootArgs; } if (bs.initrdPath !== undefined) { payload.initrd_path = bs.initrdPath; } return payload; } /** * Generate the machine config PUT payload. */ public toMachineConfigPayload(): Record { const mc = this.config.machineConfig; const payload: Record = { vcpu_count: mc.vcpuCount, mem_size_mib: mc.memSizeMib, }; if (mc.smt !== undefined) { payload.smt = mc.smt; } if (mc.cpuTemplate !== undefined) { payload.cpu_template = mc.cpuTemplate; } if (mc.trackDirtyPages !== undefined) { payload.track_dirty_pages = mc.trackDirtyPages; } return payload; } /** * Generate a drive PUT payload. */ public toDrivePayload(drive: IDriveConfig): Record { const payload: Record = { drive_id: drive.driveId, path_on_host: drive.pathOnHost, is_root_device: drive.isRootDevice, is_read_only: drive.isReadOnly ?? false, }; if (drive.partUuid !== undefined) { payload.partuuid = drive.partUuid; } if (drive.cacheType !== undefined) { payload.cache_type = drive.cacheType; } if (drive.rateLimiter) { payload.rate_limiter = this.toRateLimiterPayload(drive.rateLimiter); } if (drive.ioEngine !== undefined) { payload.io_engine = drive.ioEngine; } return payload; } /** * Generate a network interface PUT payload. */ public toNetworkInterfacePayload(iface: INetworkInterfaceConfig): Record { const payload: Record = { iface_id: iface.ifaceId, }; if (iface.hostDevName !== undefined) { payload.host_dev_name = iface.hostDevName; } if (iface.guestMac !== undefined) { payload.guest_mac = iface.guestMac; } if (iface.rxRateLimiter) { payload.rx_rate_limiter = this.toRateLimiterPayload(iface.rxRateLimiter); } if (iface.txRateLimiter) { payload.tx_rate_limiter = this.toRateLimiterPayload(iface.txRateLimiter); } return payload; } /** * Generate the vsock PUT payload. */ public toVsockPayload(): Record | null { if (!this.config.vsock) return null; return { guest_cid: this.config.vsock.guestCid, uds_path: this.config.vsock.udsPath, }; } /** * Generate the balloon PUT payload. */ public toBalloonPayload(): Record | null { if (!this.config.balloon) return null; const payload: Record = { amount_mib: this.config.balloon.amountMib, deflate_on_oom: this.config.balloon.deflateOnOom, }; if (this.config.balloon.statsPollingIntervalS !== undefined) { payload.stats_polling_interval_s = this.config.balloon.statsPollingIntervalS; } return payload; } /** * Generate the MMDS config PUT payload. */ public toMmdsConfigPayload(): Record | null { if (!this.config.mmds) return null; const payload: Record = { network_interfaces: this.config.mmds.networkInterfaces, }; if (this.config.mmds.version !== undefined) { payload.version = this.config.mmds.version; } return payload; } /** * Generate the logger PUT payload. */ public toLoggerPayload(): Record | null { if (!this.config.logger) return null; const payload: Record = { log_path: this.config.logger.logPath, }; if (this.config.logger.level !== undefined) { payload.level = this.config.logger.level; } if (this.config.logger.showLevel !== undefined) { payload.show_level = this.config.logger.showLevel; } if (this.config.logger.showLogOrigin !== undefined) { payload.show_log_origin = this.config.logger.showLogOrigin; } return payload; } /** * Generate the metrics PUT payload. */ public toMetricsPayload(): Record | null { if (!this.config.metrics) return null; return { metrics_path: this.config.metrics.metricsPath, }; } /** * Convert a rate limiter config to a Firecracker API payload. */ private toRateLimiterPayload(rl: IRateLimiter): Record { const payload: Record = {}; if (rl.bandwidth) { payload.bandwidth = { size: rl.bandwidth.size, refill_time: rl.bandwidth.refillTime, }; if (rl.bandwidth.oneTimeBurst !== undefined) { payload.bandwidth.one_time_burst = rl.bandwidth.oneTimeBurst; } } if (rl.ops) { payload.ops = { size: rl.ops.size, refill_time: rl.ops.refillTime, }; if (rl.ops.oneTimeBurst !== undefined) { payload.ops.one_time_burst = rl.ops.oneTimeBurst; } } return payload; } }