Files
smartvm/ts/classes.vmconfig.ts
2026-02-08 21:47:33 +00:00

252 lines
7.0 KiB
TypeScript

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<string, any> {
const bs = this.config.bootSource;
const payload: Record<string, any> = {
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<string, any> {
const mc = this.config.machineConfig;
const payload: Record<string, any> = {
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<string, any> {
const payload: Record<string, any> = {
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<string, any> {
const payload: Record<string, any> = {
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<string, any> | 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<string, any> | null {
if (!this.config.balloon) return null;
const payload: Record<string, any> = {
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<string, any> | null {
if (!this.config.mmds) return null;
const payload: Record<string, any> = {
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<string, any> | null {
if (!this.config.logger) return null;
const payload: Record<string, any> = {
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<string, any> | 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<string, any> {
const payload: Record<string, any> = {};
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;
}
}