initial
This commit is contained in:
251
ts/classes.vmconfig.ts
Normal file
251
ts/classes.vmconfig.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user