252 lines
7.0 KiB
TypeScript
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;
|
|
}
|
|
}
|