Firecracker MicroVM in Java: Complete Implementation Guide

Introduction to Firecracker MicroVM

Firecracker is an open-source virtualization technology that enables lightweight, secure microVMs for serverless computing, containers, and other workloads. This Java implementation provides a high-level API for managing Firecracker microVMs with full lifecycle management.

Architecture Overview

graph TB
subgraph "Firecracker Java SDK"
API[Firecracker API Client]
VM[MicroVM Manager]
NET[Network Manager]
DISK[Disk Manager]
MET[Metrics Collector]
end
subgraph "Firecracker Process"
FC[Firecracker VMM]
KVM[KVM Kernel Module]
VCPU[Virtual CPUs]
MEM[Virtual Memory]
NETV[Virtual Network]
DISKV[Virtual Disk]
end
subgraph "Guest OS"
GUEST[Linux Guest]
APP[Application]
end
API --> VM
VM --> FC
FC --> KVM
FC --> VCPU
FC --> MEM
FC --> NETV
FC --> DISKV
NETV --> GUEST
DISKV --> GUEST
GUEST --> APP

Core Dependencies

<properties>
<jackson.version>2.15.2</jackson.version>
<okhttp.version>4.11.0</okhttp.version>
<junit.version>5.10.0</junit.version>
</properties>
<dependencies>
<!-- HTTP Client for Firecracker API -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Process Management -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.4.0</version>
</dependency>
<!-- Unix Socket Support -->
<dependency>
<groupId>com.kohlschutter.unix</groupId>
<artifactId>unix-socket-factory</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Metrics -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.11.5</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

Core Models and Configuration

package com.firecracker.vm;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MicroVMConfig {
@JsonProperty("boot-source")
private BootSource bootSource;
@JsonProperty("drives")
private List<Drive> drives;
@JsonProperty("network-interfaces")
private List<NetworkInterface> networkInterfaces;
@JsonProperty("machine-config")
private MachineConfig machineConfig;
@JsonProperty("vsock")
private VsockDevice vsock;
@JsonProperty("logger")
private LoggerConfig logger;
@JsonProperty("metrics")
private MetricsConfig metrics;
@JsonProperty("mmds-config")
private MMDSConfig mmdsConfig;
@JsonProperty("balloon")
private BalloonDevice balloon;
@JsonProperty("entropy")
private EntropyDevice entropy;
}
@Data
public class BootSource {
@JsonProperty("kernel_image_path")
private String kernelImagePath;
@JsonProperty("boot_args")
private String bootArgs;
@JsonProperty("initrd_path")
private String initrdPath;
}
@Data
public class Drive {
@JsonProperty("drive_id")
private String driveId;
@JsonProperty("path_on_host")
private String pathOnHost;
@JsonProperty("is_root_device")
private boolean isRootDevice;
@JsonProperty("is_read_only")
private boolean isReadOnly;
@JsonProperty("partuuid")
private String partUuid;
@JsonProperty("cache_type")
private String cacheType = "Unsafe";
@JsonProperty("io_engine")
private String ioEngine = "Sync";
@JsonProperty("rate_limiter")
private RateLimiter rateLimiter;
}
@Data
public class RateLimiter {
@JsonProperty("bandwidth")
private TokenBucket bandwidth;
@JsonProperty("ops")
private TokenBucket ops;
}
@Data
public class TokenBucket {
@JsonProperty("size")
private long size;
@JsonProperty("one_time_burst")
private Long oneTimeBurst;
@JsonProperty("refill_time")
private long refillTime;
}
@Data
public class NetworkInterface {
@JsonProperty("iface_id")
private String ifaceId;
@JsonProperty("guest_mac")
private String guestMac;
@JsonProperty("host_dev_name")
private String hostDevName;
@JsonProperty("rx_rate_limiter")
private RateLimiter rxRateLimiter;
@JsonProperty("tx_rate_limiter")
private RateLimiter txRateLimiter;
@JsonProperty("allow_mmds_requests")
private boolean allowMmdsRequests;
}
@Data
public class MachineConfig {
@JsonProperty("vcpu_count")
private int vcpuCount = 1;
@JsonProperty("mem_size_mib")
private int memSizeMib = 128;
@JsonProperty("smt")
private boolean smt = false;
@JsonProperty("track_dirty_pages")
private boolean trackDirtyPages = false;
@JsonProperty("cpu_template")
private String cpuTemplate;
}
@Data
public class VsockDevice {
@JsonProperty("vsock_id")
private String vsockId;
@JsonProperty("guest_cid")
private int guestCid;
@JsonProperty("uds_path")
private String udsPath;
}
@Data
public class LoggerConfig {
@JsonProperty("log_path")
private String logPath;
@JsonProperty("level")
private String level;
@JsonProperty("show_level")
private boolean showLevel = true;
@JsonProperty("show_log_origin")
private boolean showLogOrigin = true;
}
@Data
public class MetricsConfig {
@JsonProperty("metrics_path")
private String metricsPath;
}
@Data
public class MMDSConfig {
@JsonProperty("ipv4_address")
private String ipv4Address;
@JsonProperty("network_interfaces")
private List<String> networkInterfaces;
}
@Data
public class BalloonDevice {
@JsonProperty("amount_mib")
private int amountMib;
@JsonProperty("deflate_on_oom")
private boolean deflateOnOom = false;
@JsonProperty("stats_polling_interval_s")
private int statsPollingIntervalS;
}
@Data
public class EntropyDevice {
@JsonProperty("rate_limiter")
private RateLimiter rateLimiter;
}
@Data
public class SnapshotConfig {
@JsonProperty("snapshot_path")
private String snapshotPath;
@JsonProperty("mem_file_path")
private String memFilePath;
@JsonProperty("version")
private String version = "1.0.0";
@JsonProperty("diff")
private boolean diff = false;
}
@Data
public class VMState {
private String state;
private boolean paused;
private int vcpuCount;
private long memorySize;
private String id;
private long uptime;
private Map<String, Object> metrics;
}

Firecracker API Client

package com.firecracker.api;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import okio.BufferedSink;
import java.io.IOException;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class FirecrackerClient {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final MediaType JSON = MediaType.get("application/json");
private final OkHttpClient httpClient;
private final String socketPath;
private final String apiUrl;
public FirecrackerClient(String socketPath) {
this.socketPath = socketPath;
this.apiUrl = "http://localhost";
// Create custom client for Unix socket communication
this.httpClient = new OkHttpClient.Builder()
.socketFactory(new UnixSocketFactory(socketPath))
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
}
public FirecrackerClient(int port) {
this.socketPath = null;
this.apiUrl = "http://localhost:" + port;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
}
// Instance management
public CompletableFuture<Void> startInstance() {
return makeRequest("PUT", "/actions", Map.of("action_type", "InstanceStart"));
}
public CompletableFuture<Void> pauseInstance() {
return makeRequest("PATCH", "/vm", Map.of("state", "Paused"));
}
public CompletableFuture<Void> resumeInstance() {
return makeRequest("PATCH", "/vm", Map.of("state", "Resumed"));
}
public CompletableFuture<Void> sendCtrlAltDel() {
return makeRequest("PUT", "/actions", Map.of("action_type", "SendCtrlAltDel"));
}
public CompletableFuture<Map<String, Object>> getInstanceInfo() {
return makeRequest("GET", "/", null);
}
// Boot source configuration
public CompletableFuture<Void> configureBootSource(Map<String, Object> bootSource) {
return makeRequest("PUT", "/boot-source", bootSource);
}
// Drive management
public CompletableFuture<Void> addDrive(String driveId, Map<String, Object> driveConfig) {
return makeRequest("PUT", "/drives/" + driveId, driveConfig);
}
public CompletableFuture<Void> updateDrive(String driveId, Map<String, Object> driveConfig) {
return makeRequest("PATCH", "/drives/" + driveId, driveConfig);
}
public CompletableFuture<Void> removeDrive(String driveId) {
return makeRequest("DELETE", "/drives/" + driveId, null);
}
// Network interface management
public CompletableFuture<Void> addNetworkInterface(String ifaceId, 
Map<String, Object> netConfig) {
return makeRequest("PUT", "/network-interfaces/" + ifaceId, netConfig);
}
public CompletableFuture<Void> updateNetworkInterface(String ifaceId, 
Map<String, Object> netConfig) {
return makeRequest("PATCH", "/network-interfaces/" + ifaceId, netConfig);
}
public CompletableFuture<Void> removeNetworkInterface(String ifaceId) {
return makeRequest("DELETE", "/network-interfaces/" + ifaceId, null);
}
// Machine configuration
public CompletableFuture<Void> configureMachine(Map<String, Object> machineConfig) {
return makeRequest("PUT", "/machine-config", machineConfig);
}
// VSOCK configuration
public CompletableFuture<Void> configureVsock(Map<String, Object> vsockConfig) {
return makeRequest("PUT", "/vsock", vsockConfig);
}
// Logger configuration
public CompletableFuture<Void> configureLogger(Map<String, Object> loggerConfig) {
return makeRequest("PUT", "/logger", loggerConfig);
}
// Metrics configuration
public CompletableFuture<Void> configureMetrics(Map<String, Object> metricsConfig) {
return makeRequest("PUT", "/metrics", metricsConfig);
}
// MMDS (MicroVM Metadata Service) configuration
public CompletableFuture<Void> configureMMDS(Map<String, Object> mmdsConfig) {
return makeRequest("PUT", "/mmds/config", mmdsConfig);
}
public CompletableFuture<Void> updateMMDS(Map<String, Object> metadata) {
return makeRequest("PUT", "/mmds", metadata);
}
public CompletableFuture<Map<String, Object>> getMMDS() {
return makeRequest("GET", "/mmds", null);
}
// Balloon device configuration
public CompletableFuture<Void> configureBalloon(Map<String, Object> balloonConfig) {
return makeRequest("PUT", "/balloon", balloonConfig);
}
public CompletableFuture<Void> updateBalloon(int amountMib) {
return makeRequest("PATCH", "/balloon", Map.of("amount_mib", amountMib));
}
public CompletableFuture<Map<String, Object>> getBalloonStats() {
return makeRequest("GET", "/balloon/statistics", null);
}
// Entropy device configuration
public CompletableFuture<Void> configureEntropy(Map<String, Object> entropyConfig) {
return makeRequest("PUT", "/entropy", entropyConfig);
}
// Snapshot operations
public CompletableFuture<Void> createSnapshot(Map<String, Object> snapshotConfig) {
return makeRequest("PUT", "/snapshot/create", snapshotConfig);
}
public CompletableFuture<Void> loadSnapshot(Map<String, Object> snapshotConfig) {
return makeRequest("PUT", "/snapshot/load", snapshotConfig);
}
// Metrics and statistics
public CompletableFuture<Map<String, Object>> getFullVMConfiguration() {
return makeRequest("GET", "/vm/config", null);
}
public CompletableFuture<Map<String, Object>> getInstanceMetrics() {
return makeRequest("GET", "/metrics", null);
}
// CPU configuration
public CompletableFuture<Void> updateVcpu(int vcpuCount) {
return makeRequest("PUT", "/machine-config", Map.of("vcpu_count", vcpuCount));
}
// Memory configuration
public CompletableFuture<Void> updateMemory(int memorySizeMib) {
return makeRequest("PATCH", "/vm", Map.of("mem_size_mib", memorySizeMib));
}
// Rate limiter configuration
public CompletableFuture<Void> updateDriveRateLimiter(String driveId, 
Map<String, Object> rateLimiter) {
return makeRequest("PATCH", "/drives/" + driveId, 
Map.of("rate_limiter", rateLimiter));
}
public CompletableFuture<Void> updateNetworkRateLimiter(String ifaceId, 
Map<String, Object> rateLimiter,
boolean isRx) {
String field = isRx ? "rx_rate_limiter" : "tx_rate_limiter";
return makeRequest("PATCH", "/network-interfaces/" + ifaceId, 
Map.of(field, rateLimiter));
}
// Helper methods
private CompletableFuture<Map<String, Object>> makeRequest(String method, 
String path, 
Object body) {
CompletableFuture<Map<String, Object>> future = new CompletableFuture<>();
try {
Request.Builder builder = new Request.Builder()
.url(apiUrl + path);
if (body != null) {
String jsonBody = MAPPER.writeValueAsString(body);
builder.method(method, RequestBody.create(jsonBody, JSON));
} else {
builder.method(method, null);
}
Request request = builder.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
if (response.isSuccessful()) {
if (responseBody != null && responseBody.contentLength() > 0) {
String responseJson = responseBody.string();
Map<String, Object> result = MAPPER.readValue(
responseJson, 
MAPPER.getTypeFactory().constructMapType(
Map.class, String.class, Object.class
)
);
future.complete(result);
} else {
future.complete(Map.of());
}
} else {
future.completeExceptionally(
new IOException("HTTP " + response.code() + ": " + 
response.message())
);
}
}
}
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(e);
}
});
} catch (Exception e) {
future.completeExceptionally(e);
}
return future;
}
private CompletableFuture<Void> makeRequest(String method, String path, Object body) {
CompletableFuture<Void> future = new CompletableFuture<>();
makeRequest(method, path, body).whenComplete((result, error) -> {
if (error != null) {
future.completeExceptionally(error);
} else {
future.complete(null);
}
});
return future;
}
public void shutdown() {
if (httpClient != null) {
httpClient.dispatcher().executorService().shutdown();
httpClient.connectionPool().evictAll();
}
}
}
// Unix Socket Factory for communicating with Firecracker's API socket
class UnixSocketFactory extends SocketFactory {
private final String socketPath;
public UnixSocketFactory(String socketPath) {
this.socketPath = socketPath;
}
@Override
public Socket createSocket() throws IOException {
return new java.net.Socket() {
@Override
public void connect(java.net.SocketAddress endpoint, int timeout) 
throws IOException {
// Unix socket connection
java.net.SocketAddress unixAddr = new java.net.UnixSocketAddress(
new java.io.File(socketPath)
);
super.connect(unixAddr, timeout);
}
};
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return createSocket();
}
@Override
public Socket createSocket(String host, int port, 
java.net.InetAddress localHost, int localPort) 
throws IOException {
return createSocket();
}
@Override
public Socket createSocket(java.net.InetAddress host, int port) throws IOException {
return createSocket();
}
@Override
public Socket createSocket(java.net.InetAddress address, int port, 
java.net.InetAddress localAddress, int localPort) 
throws IOException {
return createSocket();
}
}

MicroVM Manager

package com.firecracker.vm;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import java.io.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.*;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class MicroVMManager {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String FIRECRACKER_BINARY = "firecracker";
private static final String JAILER_BINARY = "jailer";
private final ExecutorService executor;
private final Path workDir;
private final Map<String, MicroVMInstance> instances;
private final FirecrackerClient defaultClient;
public MicroVMManager(Path workDir) {
this.workDir = workDir.toAbsolutePath();
this.executor = Executors.newCachedThreadPool();
this.instances = new ConcurrentHashMap<>();
this.defaultClient = null;
try {
Files.createDirectories(workDir);
} catch (IOException e) {
throw new RuntimeException("Failed to create work directory", e);
}
}
public MicroVMManager() {
this(Paths.get(System.getProperty("user.dir"), ".firecracker"));
}
public static class MicroVMInstance {
private final String id;
private final Process process;
private final FirecrackerClient apiClient;
private final Path socketPath;
private final Path logPath;
private final Path metricsPath;
private final MicroVMConfig config;
private volatile State state = State.CREATED;
private final CompletableFuture<Void> exitFuture;
public enum State {
CREATED, STARTING, RUNNING, PAUSED, STOPPING, TERMINATED, ERROR
}
public MicroVMInstance(String id, Process process, FirecrackerClient apiClient,
Path socketPath, Path logPath, Path metricsPath,
MicroVMConfig config) {
this.id = id;
this.process = process;
this.apiClient = apiClient;
this.socketPath = socketPath;
this.logPath = logPath;
this.metricsPath = metricsPath;
this.config = config;
this.exitFuture = new CompletableFuture<>();
// Monitor process exit
CompletableFuture.runAsync(() -> {
try {
int exitCode = process.waitFor();
state = State.TERMINATED;
exitFuture.complete(null);
cleanup();
} catch (InterruptedException e) {
exitFuture.completeExceptionally(e);
state = State.ERROR;
}
});
}
public String getId() { return id; }
public Process getProcess() { return process; }
public FirecrackerClient getApiClient() { return apiClient; }
public Path getSocketPath() { return socketPath; }
public Path getLogPath() { return logPath; }
public Path getMetricsPath() { return metricsPath; }
public MicroVMConfig getConfig() { return config; }
public State getState() { return state; }
public CompletableFuture<Void> getExitFuture() { return exitFuture; }
public void setState(State state) {
this.state = state;
}
private void cleanup() {
try {
if (Files.exists(socketPath)) {
Files.delete(socketPath);
}
if (apiClient != null) {
apiClient.shutdown();
}
} catch (IOException e) {
// Ignore cleanup errors
}
}
}
public MicroVMInstance createVM(String vmId, MicroVMConfig config) throws Exception {
return createVM(vmId, config, false);
}
public MicroVMInstance createVM(String vmId, MicroVMConfig config, boolean useJailer) 
throws Exception {
if (instances.containsKey(vmId)) {
throw new IllegalArgumentException("VM with ID " + vmId + " already exists");
}
// Create VM directory
Path vmDir = workDir.resolve(vmId);
Files.createDirectories(vmDir);
// Create socket path
Path socketPath = vmDir.resolve("firecracker.socket");
// Create log and metrics paths if configured
Path logPath = config.getLogger() != null && config.getLogger().getLogPath() != null ?
Paths.get(config.getLogger().getLogPath()) : vmDir.resolve("firecracker.log");
Path metricsPath = config.getMetrics() != null && 
config.getMetrics().getMetricsPath() != null ?
Paths.get(config.getMetrics().getMetricsPath()) : 
vmDir.resolve("firecracker.metrics");
// Start Firecracker process
Process process;
if (useJailer) {
process = startFirecrackerWithJailer(vmId, socketPath, config);
} else {
process = startFirecracker(vmId, socketPath, config);
}
// Create API client
FirecrackerClient apiClient = new FirecrackerClient(socketPath.toString());
// Wait for API socket to be ready
waitForSocket(socketPath, Duration.ofSeconds(10));
// Create instance
MicroVMInstance instance = new MicroVMInstance(
vmId, process, apiClient, socketPath, logPath, metricsPath, config
);
instances.put(vmId, instance);
// Configure the VM
configureVM(instance, config);
return instance;
}
private Process startFirecracker(String vmId, Path socketPath, MicroVMConfig config) 
throws IOException {
List<String> command = new ArrayList<>();
command.add(FIRECRACKER_BINARY);
command.add("--api-sock");
command.add(socketPath.toString());
// Add log file if configured
if (config.getLogger() != null && config.getLogger().getLogPath() != null) {
command.add("--log-path");
command.add(config.getLogger().getLogPath());
command.add("--level");
command.add(config.getLogger().getLevel() != null ? 
config.getLogger().getLevel() : "Info");
}
// Add metrics file if configured
if (config.getMetrics() != null && config.getMetrics().getMetricsPath() != null) {
command.add("--metrics-path");
command.add(config.getMetrics().getMetricsPath());
}
ProcessBuilder pb = new ProcessBuilder(command);
pb.directory(workDir.resolve(vmId).toFile());
pb.redirectErrorStream(true);
// Log output to file
Path logFile = workDir.resolve(vmId).resolve("firecracker.out");
pb.redirectOutput(logFile.toFile());
return pb.start();
}
private Process startFirecrackerWithJailer(String vmId, Path socketPath, 
MicroVMConfig config) throws IOException {
List<String> command = new ArrayList<>();
command.add(JAILER_BINARY);
command.add("--id");
command.add(vmId);
command.add("--exec-file");
command.add(FIRECRACKER_BINARY);
command.add("--uid");
command.add("1000");
command.add("--gid");
command.add("1000");
command.add("--chroot-base-dir");
command.add(workDir.resolve("jailer").toString());
command.add("--");
command.add("--api-sock");
command.add("/firecracker.socket");
// Configure logging
if (config.getLogger() != null) {
command.add("--log-path");
command.add("/firecracker.log");
command.add("--level");
command.add(config.getLogger().getLevel() != null ? 
config.getLogger().getLevel() : "Info");
}
// Configure metrics
if (config.getMetrics() != null) {
command.add("--metrics-path");
command.add("/firecracker.metrics");
}
ProcessBuilder pb = new ProcessBuilder(command);
pb.directory(workDir.resolve(vmId).toFile());
pb.redirectErrorStream(true);
// Create jailer directory structure
Path jailDir = workDir.resolve("jailer").resolve(vmId);
Files.createDirectories(jailDir);
// Copy necessary files to jail directory
if (config.getBootSource() != null && 
config.getBootSource().getKernelImagePath() != null) {
Path kernelSrc = Paths.get(config.getBootSource().getKernelImagePath());
Path kernelDest = jailDir.resolve("vmlinux");
Files.copy(kernelSrc, kernelDest, StandardCopyOption.REPLACE_EXISTING);
config.getBootSource().setKernelImagePath("/vmlinux");
}
// Copy drives
if (config.getDrives() != null) {
for (Drive drive : config.getDrives()) {
Path driveSrc = Paths.get(drive.getPathOnHost());
String driveName = driveSrc.getFileName().toString();
Path driveDest = jailDir.resolve(driveName);
Files.copy(driveSrc, driveDest, StandardCopyOption.REPLACE_EXISTING);
drive.setPathOnHost("/" + driveName);
}
}
// Update socket path for jailer
socketPath = jailDir.resolve("firecracker.socket");
Path logFile = workDir.resolve(vmId).resolve("jailer.out");
pb.redirectOutput(logFile.toFile());
return pb.start();
}
private void waitForSocket(Path socketPath, Duration timeout) throws Exception {
long endTime = System.currentTimeMillis() + timeout.toMillis();
while (System.currentTimeMillis() < endTime) {
if (Files.exists(socketPath) && 
Files.isReadable(socketPath) && 
Files.isWritable(socketPath)) {
return;
}
Thread.sleep(100);
}
throw new IOException("Firecracker API socket not ready after " + timeout);
}
private void configureVM(MicroVMInstance instance, MicroVMConfig config) throws Exception {
FirecrackerClient client = instance.getApiClient();
// Configure boot source
if (config.getBootSource() != null) {
Map<String, Object> bootSource = MAPPER.convertValue(
config.getBootSource(), Map.class
);
client.configureBootSource(bootSource).get(10, TimeUnit.SECONDS);
}
// Configure drives
if (config.getDrives() != null) {
for (Drive drive : config.getDrives()) {
Map<String, Object> driveConfig = MAPPER.convertValue(drive, Map.class);
client.addDrive(drive.getDriveId(), driveConfig).get(10, TimeUnit.SECONDS);
}
}
// Configure network interfaces
if (config.getNetworkInterfaces() != null) {
for (NetworkInterface netIface : config.getNetworkInterfaces()) {
Map<String, Object> netConfig = MAPPER.convertValue(netIface, Map.class);
client.addNetworkInterface(netIface.getIfaceId(), netConfig)
.get(10, TimeUnit.SECONDS);
}
}
// Configure machine
if (config.getMachineConfig() != null) {
Map<String, Object> machineConfig = MAPPER.convertValue(
config.getMachineConfig(), Map.class
);
client.configureMachine(machineConfig).get(10, TimeUnit.SECONDS);
}
// Configure other devices
if (config.getVsock() != null) {
Map<String, Object> vsockConfig = MAPPER.convertValue(config.getVsock(), Map.class);
client.configureVsock(vsockConfig).get(10, TimeUnit.SECONDS);
}
if (config.getBalloon() != null) {
Map<String, Object> balloonConfig = MAPPER.convertValue(
config.getBalloon(), Map.class
);
client.configureBalloon(balloonConfig).get(10, TimeUnit.SECONDS);
}
if (config.getEntropy() != null) {
Map<String, Object> entropyConfig = MAPPER.convertValue(
config.getEntropy(), Map.class
);
client.configureEntropy(entropyConfig).get(10, TimeUnit.SECONDS);
}
// Configure MMDS if needed
if (config.getMmdsConfig() != null) {
Map<String, Object> mmdsConfig = MAPPER.convertValue(
config.getMmdsConfig(), Map.class
);
client.configureMMDS(mmdsConfig).get(10, TimeUnit.SECONDS);
}
instance.setState(MicroVMInstance.State.STARTING);
}
public CompletableFuture<Void> startVM(String vmId) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
return instance.getApiClient().startInstance()
.thenRun(() -> instance.setState(MicroVMInstance.State.RUNNING))
.exceptionally(e -> {
instance.setState(MicroVMInstance.State.ERROR);
throw new RuntimeException("Failed to start VM", e);
});
}
public CompletableFuture<Void> pauseVM(String vmId) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
return instance.getApiClient().pauseInstance()
.thenRun(() -> instance.setState(MicroVMInstance.State.PAUSED));
}
public CompletableFuture<Void> resumeVM(String vmId) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
return instance.getApiClient().resumeInstance()
.thenRun(() -> instance.setState(MicroVMInstance.State.RUNNING));
}
public CompletableFuture<Void> stopVM(String vmId) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
instance.setState(MicroVMInstance.State.STOPPING);
// Send Ctrl+Alt+Del to gracefully shutdown
return instance.getApiClient().sendCtrlAltDel()
.thenCompose(v -> instance.getExitFuture())
.thenRun(() -> {
instance.setState(MicroVMInstance.State.TERMINATED);
instances.remove(vmId);
});
}
public CompletableFuture<Void> forceStopVM(String vmId) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
instance.setState(MicroVMInstance.State.STOPPING);
instance.getProcess().destroy();
return instance.getExitFuture()
.thenRun(() -> {
instance.setState(MicroVMInstance.State.TERMINATED);
instances.remove(vmId);
});
}
public CompletableFuture<Map<String, Object>> getVMInfo(String vmId) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
return instance.getApiClient().getInstanceInfo();
}
public CompletableFuture<Void> createSnapshot(String vmId, SnapshotConfig snapshotConfig) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
Map<String, Object> config = MAPPER.convertValue(snapshotConfig, Map.class);
return instance.getApiClient().createSnapshot(config);
}
public MicroVMInstance loadSnapshot(String vmId, SnapshotConfig snapshotConfig) 
throws Exception {
if (instances.containsKey(vmId)) {
throw new IllegalArgumentException("VM with ID " + vmId + " already exists");
}
// Create VM from snapshot
Path vmDir = workDir.resolve(vmId);
Files.createDirectories(vmDir);
Path socketPath = vmDir.resolve("firecracker.socket");
// Start Firecracker process
Process process = startFirecracker(vmId, socketPath, new MicroVMConfig());
// Create API client
FirecrackerClient apiClient = new FirecrackerClient(socketPath.toString());
// Wait for API socket
waitForSocket(socketPath, Duration.ofSeconds(10));
// Create instance
MicroVMInstance instance = new MicroVMInstance(
vmId, process, apiClient, socketPath, null, null, new MicroVMConfig()
);
instances.put(vmId, instance);
// Load snapshot
Map<String, Object> config = MAPPER.convertValue(snapshotConfig, Map.class);
apiClient.loadSnapshot(config).get(10, TimeUnit.SECONDS);
instance.setState(MicroVMInstance.State.RUNNING);
return instance;
}
public CompletableFuture<Void> updateVCPU(String vmId, int vcpuCount) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
return instance.getApiClient().updateVcpu(vcpuCount);
}
public CompletableFuture<Void> updateMemory(String vmId, int memorySizeMib) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
return instance.getApiClient().updateMemory(memorySizeMib);
}
public CompletableFuture<Void> addNetworkInterface(String vmId, 
NetworkInterface networkInterface) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
Map<String, Object> config = MAPPER.convertValue(networkInterface, Map.class);
return instance.getApiClient().addNetworkInterface(
networkInterface.getIfaceId(), config
);
}
public CompletableFuture<Void> addDrive(String vmId, Drive drive) {
MicroVMInstance instance = instances.get(vmId);
if (instance == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("VM not found: " + vmId)
);
}
Map<String, Object> config = MAPPER.convertValue(drive, Map.class);
return instance.getApiClient().addDrive(drive.getDriveId(), config);
}
public List<MicroVMInstance> listVMs() {
return new ArrayList<>(instances.values());
}
public MicroVMInstance getVM(String vmId) {
return instances.get(vmId);
}
public void shutdown() {
// Stop all VMs
List<CompletableFuture<Void>> stopFutures = new ArrayList<>();
for (String vmId : instances.keySet()) {
stopFutures.add(forceStopVM(vmId));
}
// Wait for all VMs to stop
CompletableFuture.allOf(stopFutures.toArray(new CompletableFuture[0]))
.join();
// Shutdown executor
executor.shutdown();
try {
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

Network Manager for MicroVMs

package com.firecracker.network;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
public class MicroVMNetworkManager {
private static final String TAP_INTERFACE_PREFIX = "fc-tap";
private static final String BRIDGE_NAME = "fc-br0";
private final ExecutorService executor;
private final Map<String, TapInterface> tapInterfaces;
private final Set<String> bridges;
private final Path networkConfigDir;
public MicroVMNetworkManager() {
this.executor = Executors.newCachedThreadPool();
this.tapInterfaces = new ConcurrentHashMap<>();
this.bridges = ConcurrentHashMap.newKeySet();
this.networkConfigDir = Paths.get("/etc/firecracker/network");
try {
Files.createDirectories(networkConfigDir);
} catch (IOException e) {
System.err.println("Failed to create network config directory: " + e.getMessage());
}
}
public static class TapInterface {
private final String name;
private final String macAddress;
private final String ipAddress;
private final String bridge;
private final String vmId;
private volatile boolean isUp;
public TapInterface(String name, String macAddress, String ipAddress, 
String bridge, String vmId) {
this.name = name;
this.macAddress = macAddress;
this.ipAddress = ipAddress;
this.bridge = bridge;
this.vmId = vmId;
this.isUp = false;
}
public String getName() { return name; }
public String getMacAddress() { return macAddress; }
public String getIpAddress() { return ipAddress; }
public String getBridge() { return bridge; }
public String getVmId() { return vmId; }
public boolean isUp() { return isUp; }
public void setUp(boolean up) { this.isUp = up; }
}
public TapInterface createTapInterface(String vmId, String macAddress) 
throws IOException, InterruptedException {
String tapName = TAP_INTERFACE_PREFIX + "-" + vmId;
// Check if tap interface already exists
if (tapInterfaces.containsKey(tapName)) {
throw new IllegalArgumentException("Tap interface already exists: " + tapName);
}
// Create tap interface
createTapDevice(tapName);
// Generate IP address
String ipAddress = generateIpAddress(vmId);
// Create bridge if it doesn't exist
ensureBridgeExists(BRIDGE_NAME);
// Add tap to bridge
addInterfaceToBridge(tapName, BRIDGE_NAME);
// Configure IP on bridge if needed
configureBridgeIp(BRIDGE_NAME);
// Bring up the interface
bringInterfaceUp(tapName);
TapInterface tap = new TapInterface(tapName, macAddress, ipAddress, 
BRIDGE_NAME, vmId);
tap.setUp(true);
tapInterfaces.put(tapName, tap);
// Save network configuration
saveNetworkConfig(tap);
return tap;
}
private void createTapDevice(String tapName) throws IOException, InterruptedException {
ProcessBuilder pb = new ProcessBuilder(
"ip", "tuntap", "add", "dev", tapName, "mode", "tap"
);
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
String error = readProcessOutput(process.getErrorStream());
throw new IOException("Failed to create tap device: " + error);
}
}
private void ensureBridgeExists(String bridgeName) throws IOException, InterruptedException {
// Check if bridge exists
ProcessBuilder checkPb = new ProcessBuilder("brctl", "show", bridgeName);
Process checkProcess = checkPb.start();
int checkExit = checkProcess.waitFor();
if (checkExit != 0) {
// Create bridge
ProcessBuilder createPb = new ProcessBuilder(
"brctl", "addbr", bridgeName
);
Process createProcess = createPb.start();
int createExit = createProcess.waitFor();
if (createExit != 0) {
String error = readProcessOutput(createProcess.getErrorStream());
throw new IOException("Failed to create bridge: " + error);
}
bridges.add(bridgeName);
// Bring bridge up
bringInterfaceUp(bridgeName);
}
}
private void addInterfaceToBridge(String interfaceName, String bridgeName) 
throws IOException, InterruptedException {
ProcessBuilder pb = new ProcessBuilder(
"brctl", "addif", bridgeName, interfaceName
);
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
String error = readProcessOutput(process.getErrorStream());
throw new IOException("Failed to add interface to bridge: " + error);
}
}
private void configureBridgeIp(String bridgeName) throws IOException, InterruptedException {
// Configure bridge with IP for host connectivity
String bridgeIp = "192.168.100.1/24";
ProcessBuilder pb = new ProcessBuilder(
"ip", "addr", "add", bridgeIp, "dev", bridgeName
);
Process process = pb.start();
process.waitFor();
// Don't fail if IP already exists
}
private void bringInterfaceUp(String interfaceName) 
throws IOException, InterruptedException {
ProcessBuilder pb = new ProcessBuilder(
"ip", "link", "set", interfaceName, "up"
);
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
String error = readProcessOutput(process.getErrorStream());
throw new IOException("Failed to bring interface up: " + error);
}
}
private String generateIpAddress(String vmId) {
// Generate deterministic IP based on VM ID hash
int hash = vmId.hashCode() & 0xFF; // Get last byte
return String.format("192.168.100.%d", 100 + (hash % 100));
}
private void saveNetworkConfig(TapInterface tap) throws IOException {
Path configFile = networkConfigDir.resolve(tap.getName() + ".json");
Map<String, Object> config = new HashMap<>();
config.put("name", tap.getName());
config.put("mac_address", tap.getMacAddress());
config.put("ip_address", tap.getIpAddress());
config.put("bridge", tap.getBridge());
config.put("vm_id", tap.getVmId());
config.put("created", new Date().toString());
String json = new com.fasterxml.jackson.databind.ObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(config);
Files.writeString(configFile, json);
}
public void removeTapInterface(String tapName) throws IOException, InterruptedException {
TapInterface tap = tapInterfaces.remove(tapName);
if (tap == null) {
throw new IllegalArgumentException("Tap interface not found: " + tapName);
}
// Bring interface down
ProcessBuilder downPb = new ProcessBuilder(
"ip", "link", "set", tapName, "down"
);
downPb.start().waitFor();
// Remove from bridge
ProcessBuilder removePb = new ProcessBuilder(
"brctl", "delif", tap.getBridge(), tapName
);
removePb.start().waitFor();
// Delete tap device
ProcessBuilder deletePb = new ProcessBuilder(
"ip", "tuntap", "del", "dev", tapName, "mode", "tap"
);
deletePb.start().waitFor();
// Remove config file
Path configFile = networkConfigDir.resolve(tapName + ".json");
Files.deleteIfExists(configFile);
}
public void cleanupAllInterfaces() {
for (String tapName : new ArrayList<>(tapInterfaces.keySet())) {
try {
removeTapInterface(tapName);
} catch (Exception e) {
System.err.println("Failed to remove tap interface " + tapName + ": " + e.getMessage());
}
}
// Remove bridges
for (String bridgeName : bridges) {
try {
ProcessBuilder pb = new ProcessBuilder("ip", "link", "set", bridgeName, "down");
pb.start().waitFor();
pb = new ProcessBuilder("brctl", "delbr", bridgeName);
pb.start().waitFor();
} catch (Exception e) {
System.err.println("Failed to remove bridge " + bridgeName + ": " + e.getMessage());
}
}
bridges.clear();
}
public TapInterface getTapInterface(String tapName) {
return tapInterfaces.get(tapName);
}
public List<TapInterface> getAllTapInterfaces() {
return new ArrayList<>(tapInterfaces.values());
}
public void configureFirewallRules(String vmId, String tapName) 
throws IOException, InterruptedException {
// Allow traffic from VM
ProcessBuilder pb = new ProcessBuilder(
"iptables", "-A", "FORWARD", "-i", tapName, "-j", "ACCEPT"
);
pb.start().waitFor();
// Allow traffic to VM
pb = new ProcessBuilder(
"iptables", "-A", "FORWARD", "-o", tapName, "-j", "ACCEPT"
);
pb.start().waitFor();
// NAT for outbound traffic
pb = new ProcessBuilder(
"iptables", "-t", "nat", "-A", "POSTROUTING", "-s", 
generateIpAddress(vmId), "-j", "MASQUERADE"
);
pb.start().waitFor();
}
public void removeFirewallRules(String vmId, String tapName) 
throws IOException, InterruptedException {
// Remove NAT rule
ProcessBuilder pb = new ProcessBuilder(
"iptables", "-t", "nat", "-D", "POSTROUTING", "-s", 
generateIpAddress(vmId), "-j", "MASQUERADE"
);
pb.start().waitFor();
// Remove forward rules
pb = new ProcessBuilder(
"iptables", "-D", "FORWARD", "-i", tapName, "-j", "ACCEPT"
);
pb.start().waitFor();
pb = new ProcessBuilder(
"iptables", "-D", "FORWARD", "-o", tapName, "-j", "ACCEPT"
);
pb.start().waitFor();
}
private String readProcessOutput(InputStream inputStream) throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream))) {
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
return output.toString();
}
}
public void shutdown() {
cleanupAllInterfaces();
executor.shutdown();
try {
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

Disk Image Manager

package com.firecracker.disk;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.zip.*;
public class MicroVMDiskManager {
private final ExecutorService executor;
private final Path baseImageDir;
private final Map<String, DiskImage> diskImages;
public MicroVMDiskManager(Path baseImageDir) {
this.executor = Executors.newCachedThreadPool();
this.baseImageDir = baseImageDir.toAbsolutePath();
this.diskImages = new ConcurrentHashMap<>();
try {
Files.createDirectories(baseImageDir);
} catch (IOException e) {
throw new RuntimeException("Failed to create base image directory", e);
}
}
public MicroVMDiskManager() {
this(Paths.get(System.getProperty("user.dir"), ".firecracker", "images"));
}
public static class DiskImage {
private final String id;
private final Path path;
private final long size;
private final String format; // raw, qcow2, ext4
private final boolean readOnly;
private final Map<String, String> metadata;
public DiskImage(String id, Path path, long size, String format, 
boolean readOnly, Map<String, String> metadata) {
this.id = id;
this.path = path;
this.size = size;
this.format = format;
this.readOnly = readOnly;
this.metadata = metadata != null ? new HashMap<>(metadata) : new HashMap<>();
}
public String getId() { return id; }
public Path getPath() { return path; }
public long getSize() { return size; }
public String getFormat() { return format; }
public boolean isReadOnly() { return readOnly; }
public Map<String, String> getMetadata() { return Collections.unmodifiableMap(metadata); }
public void addMetadata(String key, String value) {
metadata.put(key, value);
}
}
public DiskImage createDiskImage(String imageId, long sizeMB, String format) 
throws IOException {
Path imagePath = baseImageDir.resolve(imageId + "." + format);
if (Files.exists(imagePath)) {
throw new IllegalArgumentException("Image already exists: " + imageId);
}
// Create empty disk image
createEmptyImage(imagePath, sizeMB, format);
DiskImage diskImage = new DiskImage(imageId, imagePath, sizeMB * 1024 * 1024, 
format, false, new HashMap<>());
diskImages.put(imageId, diskImage);
return diskImage;
}
private void createEmptyImage(Path imagePath, long sizeMB, String format) 
throws IOException {
long sizeBytes = sizeMB * 1024 * 1024;
switch (format.toLowerCase()) {
case "raw":
createRawImage(imagePath, sizeBytes);
break;
case "qcow2":
createQcow2Image(imagePath, sizeBytes);
break;
case "ext4":
createExt4Image(imagePath, sizeBytes);
break;
default:
throw new IllegalArgumentException("Unsupported image format: " + format);
}
}
private void createRawImage(Path imagePath, long sizeBytes) throws IOException {
try (RandomAccessFile file = new RandomAccessFile(imagePath.toFile(), "rw")) {
file.setLength(sizeBytes);
}
}
private void createQcow2Image(Path imagePath, long sizeBytes) throws IOException {
// For qcow2, we'd need qemu-img or similar tool
// For simplicity, creating raw and converting would require external tool
createRawImage(imagePath, sizeBytes);
System.err.println("Note: qcow2 support requires qemu-img. Created raw image instead.");
}
private void createExt4Image(Path imagePath, long sizeBytes) throws IOException {
// Create raw image first
createRawImage(imagePath, sizeBytes);
// Format as ext4 (requires mkfs.ext4)
try {
ProcessBuilder pb = new ProcessBuilder(
"mkfs.ext4", "-F", imagePath.toString()
);
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Failed to format ext4 image");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while formatting ext4 image", e);
}
}
public DiskImage importDiskImage(String imageId, Path sourcePath, String format) 
throws IOException {
Path destPath = baseImageDir.resolve(imageId + "." + format);
if (Files.exists(destPath)) {
throw new IllegalArgumentException("Image already exists: " + imageId);
}
Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING);
long size = Files.size(destPath);
DiskImage diskImage = new DiskImage(imageId, destPath, size, format, 
false, new HashMap<>());
diskImages.put(imageId, diskImage);
return diskImage;
}
public DiskImage createCopyOnWriteImage(String baseImageId, String newImageId) 
throws IOException {
DiskImage baseImage = diskImages.get(baseImageId);
if (baseImage == null) {
throw new IllegalArgumentException("Base image not found: " + baseImageId);
}
Path cowPath = baseImageDir.resolve(newImageId + ".qcow2");
// Create qcow2 image with backing file
createQcow2WithBacking(cowPath, baseImage.getPath());
long size = Files.size(cowPath);
DiskImage cowImage = new DiskImage(newImageId, cowPath, size, "qcow2", 
false, new HashMap<>());
cowImage.addMetadata("backing_file", baseImageId);
diskImages.put(newImageId, cowImage);
return cowImage;
}
private void createQcow2WithBacking(Path cowPath, Path backingPath) throws IOException {
// This would require qemu-img for proper qcow2 with backing file
// For now, create a simple copy
Files.copy(backingPath, cowPath, StandardCopyOption.REPLACE_EXISTING);
System.err.println("Note: Proper qcow2 with backing requires qemu-img");
}
public void prepareRootFS(String imageId, String rootFSPath) throws IOException {
DiskImage image = diskImages.get(imageId);
if (image == null) {
throw new IllegalArgumentException("Image not found: " + imageId);
}
// Mount and prepare root filesystem
// This is a simplified version - actual implementation would use loop devices
if (image.getFormat().equals("ext4")) {
prepareExt4RootFS(image.getPath(), rootFSPath);
}
}
private void prepareExt4RootFS(Path imagePath, String rootFSPath) throws IOException {
// This would require mounting the image, copying files, etc.
// For now, just create a placeholder
System.err.println("RootFS preparation requires mounting (not implemented)");
}
public DiskImage createFromTemplate(String templateName, String imageId) 
throws IOException {
Path templatePath = baseImageDir.resolve("templates").resolve(templateName + ".tar.gz");
if (!Files.exists(templatePath)) {
throw new IllegalArgumentException("Template not found: " + templateName);
}
Path imagePath = baseImageDir.resolve(imageId + ".ext4");
// Create empty ext4 image
createExt4Image(imagePath, 1024); // 1GB
// Extract template to image (simplified)
// Actual implementation would mount and extract
DiskImage diskImage = new DiskImage(imageId, imagePath, Files.size(imagePath), 
"ext4", false, new HashMap<>());
diskImage.addMetadata("template", templateName);
diskImages.put(imageId, diskImage);
return diskImage;
}
public void exportDiskImage(String imageId, Path exportPath) throws IOException {
DiskImage image = diskImages.get(imageId);
if (image == null) {
throw new IllegalArgumentException("Image not found: " + imageId);
}
Files.copy(image.getPath(), exportPath, StandardCopyOption.REPLACE_EXISTING);
}
public void deleteDiskImage(String imageId) throws IOException {
DiskImage image = diskImages.remove(imageId);
if (image == null) {
throw new IllegalArgumentException("Image not found: " + imageId);
}
Files.deleteIfExists(image.getPath());
}
public DiskImage getDiskImage(String imageId) {
return diskImages.get(imageId);
}
public List<DiskImage> listDiskImages() {
return new ArrayList<>(diskImages.values());
}
public CompletableFuture<DiskImage> createDiskImageAsync(String imageId, 
long sizeMB, 
String format) {
return CompletableFuture.supplyAsync(() -> {
try {
return createDiskImage(imageId, sizeMB, format);
} catch (IOException e) {
throw new CompletionException(e);
}
}, executor);
}
public void cleanup() {
// Delete all temporary images
for (DiskImage image : diskImages.values()) {
if (image.getMetadata().containsKey("temporary")) {
try {
Files.deleteIfExists(image.getPath());
} catch (IOException e) {
System.err.println("Failed to delete temporary image: " + image.getId());
}
}
}
executor.shutdown();
try {
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

Metrics and Monitoring

package com.firecracker.metrics;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import java.io.IOException;
import java.nio.file.*;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
public class MicroVMMetricsCollector {
private static final ObjectMapper MAPPER = new ObjectMapper();
private final MeterRegistry meterRegistry;
private final ScheduledExecutorService scheduler;
private final Map<String, VMMetrics> vmMetrics;
private final Map<String, ScheduledFuture<?>> scheduledTasks;
public MicroVMMetricsCollector(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.scheduler = Executors.newScheduledThreadPool(2);
this.vmMetrics = new ConcurrentHashMap<>();
this.scheduledTasks = new ConcurrentHashMap<>();
}
public static class VMMetrics {
private final String vmId;
private final AtomicLong cpuTimeMs;
private final AtomicLong memoryUsageBytes;
private final AtomicLong networkRxBytes;
private final AtomicLong networkTxBytes;
private final AtomicLong diskReadBytes;
private final AtomicLong diskWriteBytes;
private final AtomicLong uptimeSeconds;
private final Counter vcpuExits;
private final Timer apiLatency;
public VMMetrics(String vmId, MeterRegistry registry) {
this.vmId = vmId;
this.cpuTimeMs = new AtomicLong(0);
this.memoryUsageBytes = new AtomicLong(0);
this.networkRxBytes = new AtomicLong(0);
this.networkTxBytes = new AtomicLong(0);
this.diskReadBytes = new AtomicLong(0);
this.diskWriteBytes = new AtomicLong(0);
this.uptimeSeconds = new AtomicLong(0);
// Register metrics with Micrometer
Gauge.builder("firecracker.vm.cpu_time_ms", cpuTimeMs, AtomicLong::get)
.tag("vm_id", vmId)
.register(registry);
Gauge.builder("firecracker.vm.memory_usage_bytes", memoryUsageBytes, AtomicLong::get)
.tag("vm_id", vmId)
.register(registry);
Gauge.builder("firecracker.vm.network_rx_bytes", networkRxBytes, AtomicLong::get)
.tag("vm_id", vmId)
.register(registry);
Gauge.builder("firecracker.vm.network_tx_bytes", networkTxBytes, AtomicLong::get)
.tag("vm_id", vmId)
.register(registry);
Gauge.builder("firecracker.vm.disk_read_bytes", diskReadBytes, AtomicLong::get)
.tag("vm_id", vmId)
.register(registry);
Gauge.builder("firecracker.vm.disk_write_bytes", diskWriteBytes, AtomicLong::get)
.tag("vm_id", vmId)
.register(registry);
Gauge.builder("firecracker.vm.uptime_seconds", uptimeSeconds, AtomicLong::get)
.tag("vm_id", vmId)
.register(registry);
this.vcpuExits = Counter.builder("firecracker.vm.vcpu_exits")
.tag("vm_id", vmId)
.register(registry);
this.apiLatency = Timer.builder("firecracker.api.latency")
.tag("vm_id", vmId)
.register(registry);
}
public void updateFromMetrics(Map<String, Object> metricsData) {
// Parse Firecracker metrics JSON
if (metricsData.containsKey("cpu_time_ms")) {
cpuTimeMs.set(((Number) metricsData.get("cpu_time_ms")).longValue());
}
if (metricsData.containsKey("memory_usage_bytes")) {
memoryUsageBytes.set(((Number) metricsData.get("memory_usage_bytes")).longValue());
}
// Update network metrics
updateNetworkMetrics(metricsData);
// Update disk metrics
updateDiskMetrics(metricsData);
// Update VCPU exit counts
if (metricsData.containsKey("vcpu_exits")) {
vcpuExits.increment(((Number) metricsData.get("vcpu_exits")).longValue());
}
uptimeSeconds.increment(1); // Increment every collection
}
private void updateNetworkMetrics(Map<String, Object> metricsData) {
// Parse network interface metrics
if (metricsData.containsKey("net_rx_bytes")) {
networkRxBytes.set(((Number) metricsData.get("net_rx_bytes")).longValue());
}
if (metricsData.containsKey("net_tx_bytes")) {
networkTxBytes.set(((Number) metricsData.get("net_tx_bytes")).longValue());
}
}
private void updateDiskMetrics(Map<String, Object> metricsData) {
// Parse block device metrics
if (metricsData.containsKey("block_read_bytes")) {
diskReadBytes.set(((Number) metricsData.get("block_read_bytes")).longValue());
}
if (metricsData.containsKey("block_write_bytes")) {
diskWriteBytes.set(((Number) metricsData.get("block_write_bytes")).longValue());
}
}
public Timer.Sample startApiCall() {
return Timer.start(meterRegistry);
}
public void recordApiCall(Timer.Sample sample, String operation) {
sample.stop(apiLatency);
}
public String getVmId() { return vmId; }
}
public void startMonitoring(String vmId, Path metricsFilePath, 
com.firecracker.api.FirecrackerClient apiClient) {
VMMetrics metrics = new VMMetrics(vmId, meterRegistry);
vmMetrics.put(vmId, metrics);
// Schedule periodic metrics collection
ScheduledFuture<?> task = scheduler.scheduleAtFixedRate(() -> {
try {
collectMetrics(vmId, metricsFilePath, apiClient, metrics);
} catch (Exception e) {
System.err.println("Failed to collect metrics for VM " + vmId + ": " + e.getMessage());
}
}, 5, 5, TimeUnit.SECONDS); // Collect every 5 seconds
scheduledTasks.put(vmId, task);
}
private void collectMetrics(String vmId, Path metricsFilePath, 
com.firecracker.api.FirecrackerClient apiClient,
VMMetrics metrics) throws IOException {
// Method 1: Read from metrics file
if (Files.exists(metricsFilePath)) {
try {
String metricsJson = Files.readString(metricsFilePath);
Map<String, Object> metricsData = MAPPER.readValue(
metricsJson, 
MAPPER.getTypeFactory().constructMapType(Map.class, String.class, Object.class)
);
metrics.updateFromMetrics(metricsData);
// Clear the metrics file after reading
Files.writeString(metricsFilePath, "");
} catch (IOException e) {
// File might be locked, try API method
}
}
// Method 2: Use API to get metrics
try {
Timer.Sample sample = metrics.startApiCall();
apiClient.getInstanceMetrics()
.thenAccept(apiMetrics -> {
metrics.recordApiCall(sample, "get_metrics");
metrics.updateFromMetrics(apiMetrics);
})
.get(2, TimeUnit.SECONDS); // Wait with timeout
} catch (Exception e) {
// API might not be available
}
}
public void stopMonitoring(String vmId) {
ScheduledFuture<?> task = scheduledTasks.remove(vmId);
if (task != null) {
task.cancel(false);
}
vmMetrics.remove(vmId);
}
public VMMetrics getMetrics(String vmId) {
return vmMetrics.get(vmId);
}
public Map<String, VMMetrics> getAllMetrics() {
return new HashMap<>(vmMetrics);
}
public CompletableFuture<Map<String, Object>> getAggregatedMetrics() {
return CompletableFuture.supplyAsync(() -> {
Map<String, Object> aggregated = new HashMap<>();
long totalCpuTime = 0;
long totalMemory = 0;
long totalNetworkRx = 0;
long totalNetworkTx = 0;
long totalDiskRead = 0;
long totalDiskWrite = 0;
for (VMMetrics metrics : vmMetrics.values()) {
// Note: These would need to be accessible from VMMetrics
// For now, this is a placeholder
}
aggregated.put("total_vms", vmMetrics.size());
aggregated.put("total_cpu_time_ms", totalCpuTime);
aggregated.put("total_memory_bytes", totalMemory);
aggregated.put("total_network_rx_bytes", totalNetworkRx);
aggregated.put("total_network_tx_bytes", totalNetworkTx);
aggregated.put("total_disk_read_bytes", totalDiskRead);
aggregated.put("total_disk_write_bytes", totalDiskWrite);
return aggregated;
}, scheduler);
}
public void shutdown() {
// Stop all monitoring tasks
for (ScheduledFuture<?> task : scheduledTasks.values()) {
task.cancel(false);
}
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(10, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

Firecracker CLI Tool

package com.firecracker.cli;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.firecracker.vm.MicroVMManager;
import com.firecracker.vm.MicroVMConfig;
import com.firecracker.disk.MicroVMDiskManager;
import com.firecracker.network.MicroVMNetworkManager;
import com.firecracker.metrics.MicroVMMetricsCollector;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.nio.file.*;
import java.util.concurrent.Callable;
@Command(name = "fc-java", 
mixinStandardHelpOptions = true, 
version = "Firecracker Java SDK 1.0",
description = "Firecracker MicroVM Management in Java")
public class FirecrackerCLI {
@Command(name = "create", description = "Create a new MicroVM")
static class CreateCommand implements Callable<Integer> {
@Parameters(index = "0", description = "VM ID")
private String vmId;
@Option(names = {"--config", "-c"}, 
description = "Configuration file (JSON/YAML)")
private Path configFile;
@Option(names = {"--kernel", "-k"}, 
description = "Kernel image path", 
required = true)
private Path kernelPath;
@Option(names = {"--rootfs", "-r"}, 
description = "Root filesystem image")
private Path rootfsPath;
@Option(names = {"--memory", "-m"}, 
description = "Memory size in MiB", 
defaultValue = "128")
private int memoryMib;
@Option(names = {"--vcpus", "-p"}, 
description = "Number of vCPUs", 
defaultValue = "1")
private int vcpuCount;
@Option(names = {"--jailer", "-j"}, 
description = "Use jailer for isolation")
private boolean useJailer;
@Override
public Integer call() throws Exception {
MicroVMManager vmManager = new MicroVMManager();
MicroVMConfig config;
if (configFile != null && Files.exists(configFile)) {
ObjectMapper mapper;
if (configFile.toString().endsWith(".yaml") || 
configFile.toString().endsWith(".yml")) {
mapper = new ObjectMapper(new YAMLFactory());
} else {
mapper = new ObjectMapper();
}
config = mapper.readValue(configFile.toFile(), MicroVMConfig.class);
} else {
config = createDefaultConfig();
}
// Update with command line parameters
config.getBootSource().setKernelImagePath(kernelPath.toString());
config.getMachineConfig().setMemSizeMib(memoryMib);
config.getMachineConfig().setVcpuCount(vcpuCount);
if (rootfsPath != null) {
config.getDrives().get(0).setPathOnHost(rootfsPath.toString());
}
MicroVMManager.MicroVMInstance instance = vmManager.createVM(vmId, config, useJailer);
System.out.println("✓ MicroVM created: " + vmId);
System.out.println("  Socket: " + instance.getSocketPath());
System.out.println("  State: " + instance.getState());
return 0;
}
private MicroVMConfig createDefaultConfig() {
MicroVMConfig config = new MicroVMConfig();
MicroVMConfig.BootSource bootSource = new MicroVMConfig.BootSource();
bootSource.setBootArgs("console=ttyS0 reboot=k panic=1 pci=off");
config.setBootSource(bootSource);
MicroVMConfig.Drive rootDrive = new MicroVMConfig.Drive();
rootDrive.setDriveId("rootfs");
rootDrive.setPathOnHost("/dev/null"); // Placeholder
rootDrive.setRootDevice(true);
rootDrive.setReadOnly(false);
config.setDrives(java.util.Arrays.asList(rootDrive));
MicroVMConfig.MachineConfig machineConfig = new MicroVMConfig.MachineConfig();
machineConfig.setVcpuCount(1);
machineConfig.setMemSizeMib(128);
config.setMachineConfig(machineConfig);
return config;
}
}
@Command(name = "start", description = "Start a MicroVM")
static class StartCommand implements Callable<Integer> {
@Parameters(index = "0", description = "VM ID")
private String vmId;
@Override
public Integer call() throws Exception {
MicroVMManager vmManager = new MicroVMManager();
MicroVMManager.MicroVMInstance instance = vmManager.getVM(vmId);
if (instance == null) {
System.err.println("VM not found: " + vmId);
return 1;
}
vmManager.startVM(vmId).get();
System.out.println("✓ MicroVM started: " + vmId);
System.out.println("  State: " + instance.getState());
return 0;
}
}
@Command(name = "stop", description = "Stop a MicroVM")
static class StopCommand implements Callable<Integer> {
@Parameters(index = "0", description = "VM ID")
private String vmId;
@Option(names = {"--force", "-f"}, 
description = "Force stop (kill)")
private boolean force;
@Override
public Integer call() throws Exception {
MicroVMManager vmManager = new MicroVMManager();
if (force) {
vmManager.forceStopVM(vmId).get();
System.out.println("✓ MicroVM force stopped: " + vmId);
} else {
vmManager.stopVM(vmId).get();
System.out.println("✓ MicroVM stopped: " + vmId);
}
return 0;
}
}
@Command(name = "list", description = "List all MicroVMs")
static class ListCommand implements Callable<Integer> {
@Option(names = {"--all", "-a"}, 
description = "Show all VMs including terminated")
private boolean showAll;
@Override
public Integer call() throws Exception {
MicroVMManager vmManager = new MicroVMManager();
System.out.println("MicroVMs:");
System.out.println("=========");
for (MicroVMManager.MicroVMInstance instance : vmManager.listVMs()) {
System.out.println("ID: " + instance.getId());
System.out.println("  State: " + instance.getState());
System.out.println("  Socket: " + instance.getSocketPath());
System.out.println("  PID: " + instance.getProcess().pid());
System.out.println();
}
return 0;
}
}
@Command(name = "info", description = "Show VM information")
static class InfoCommand implements Callable<Integer> {
@Parameters(index = "0", description = "VM ID")
private String vmId;
@Option(names = {"--detailed", "-d"}, 
description = "Show detailed configuration")
private boolean detailed;
@Override
public Integer call() throws Exception {
MicroVMManager vmManager = new MicroVMManager();
MicroVMManager.MicroVMInstance instance = vmManager.getVM(vmId);
if (instance == null) {
System.err.println("VM not found: " + vmId);
return 1;
}
System.out.println("MicroVM: " + vmId);
System.out.println("=========");
System.out.println("State: " + instance.getState());
System.out.println("Socket: " + instance.getSocketPath());
System.out.println("PID: " + instance.getProcess().pid());
if (detailed) {
java.util.Map<String, Object> info = 
instance.getApiClient().getInstanceInfo().get();
ObjectMapper mapper = new ObjectMapper();
System.out.println("\nDetailed Configuration:");
System.out.println(mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(info));
}
return 0;
}
}
@Command(name = "snapshot", description = "Snapshot operations")
static class SnapshotCommand {
@Command(name = "create", description = "Create a snapshot")
Integer create(
@Parameters(index = "0", description = "VM ID")
String vmId,
@Option(names = {"--output", "-o"}, 
description = "Output directory for snapshot",
required = true)
Path outputDir
) throws Exception {
MicroVMManager vmManager = new MicroVMManager();
com.firecracker.vm.SnapshotConfig snapshotConfig = 
new com.firecracker.vm.SnapshotConfig();
snapshotConfig.setSnapshotPath(outputDir.resolve(vmId + ".snapshot").toString());
snapshotConfig.setMemFilePath(outputDir.resolve(vmId + ".mem").toString());
vmManager.createSnapshot(vmId, snapshotConfig).get();
System.out.println("✓ Snapshot created: " + vmId);
System.out.println("  Snapshot: " + snapshotConfig.getSnapshotPath());
System.out.println("  Memory: " + snapshotConfig.getMemFilePath());
return 0;
}
@Command(name = "load", description = "Load a snapshot")
Integer load(
@Parameters(index = "0", description = "New VM ID")
String vmId,
@Option(names = {"--snapshot", "-s"}, 
description = "Snapshot file",
required = true)
Path snapshotFile,
@Option(names = {"--memory", "-m"}, 
description = "Memory file",
required = true)
Path memoryFile
) throws Exception {
MicroVMManager vmManager = new MicroVMManager();
com.firecracker.vm.SnapshotConfig snapshotConfig = 
new com.firecracker.vm.SnapshotConfig();
snapshotConfig.setSnapshotPath(snapshotFile.toString());
snapshotConfig.setMemFilePath(memoryFile.toString());
vmManager.loadSnapshot(vmId, snapshotConfig);
System.out.println("✓ Snapshot loaded: " + vmId);
System.out.println("  From snapshot: " + snapshotFile);
return 0;
}
}
@Command(name = "network", description = "Network management")
static class NetworkCommand {
@Command(name = "create-tap", description = "Create a TAP interface")
Integer createTap(
@Parameters(index = "0", description = "VM ID")
String vmId,
@Option(names = {"--mac", "-m"}, 
description = "MAC address (optional)")
String macAddress
) throws Exception {
MicroVMNetworkManager networkManager = new MicroVMNetworkManager();
if (macAddress == null) {
// Generate a MAC address
macAddress = generateMacAddress(vmId);
}
MicroVMNetworkManager.TapInterface tap = 
networkManager.createTapInterface(vmId, macAddress);
System.out.println("✓ TAP interface created: " + tap.getName());
System.out.println("  MAC: " + tap.getMacAddress());
System.out.println("  IP: " + tap.getIpAddress());
System.out.println("  Bridge: " + tap.getBridge());
return 0;
}
private String generateMacAddress(String vmId) {
// Generate deterministic MAC from VM ID
int hash = vmId.hashCode();
return String.format("02:%02x:%02x:%02x:%02x:%02x",
(hash >> 16) & 0xFF,
(hash >> 8) & 0xFF,
hash & 0xFF,
((hash >> 24) & 0xFF) | 0x02, // Ensure locally administered
(hash >> 16) & 0xFF,
(hash >> 8) & 0xFF
);
}
}
@Command(name = "disk", description = "Disk image management")
static class DiskCommand {
@Command(name = "create", description = "Create a disk image")
Integer create(
@Parameters(index = "0", description = "Image ID")
String imageId,
@Option(names = {"--size", "-s"}, 
description = "Size in MB",
defaultValue = "1024")
int sizeMB,
@Option(names = {"--format", "-f"}, 
description = "Image format: raw, qcow2, ext4",
defaultValue = "ext4")
String format
) throws Exception {
MicroVMDiskManager diskManager = new MicroVMDiskManager();
MicroVMDiskManager.DiskImage image = 
diskManager.createDiskImage(imageId, sizeMB, format);
System.out.println("✓ Disk image created: " + imageId);
System.out.println("  Path: " + image.getPath());
System.out.println("  Size: " + image.getSize() + " bytes");
System.out.println("  Format: " + image.getFormat());
return 0;
}
@Command(name = "list", description = "List disk images")
Integer list() {
MicroVMDiskManager diskManager = new MicroVMDiskManager();
System.out.println("Disk Images:");
System.out.println("============");
for (MicroVMDiskManager.DiskImage image : diskManager.listDiskImages()) {
System.out.println("ID: " + image.getId());
System.out.println("  Path: " + image.getPath());
System.out.println("  Size: " + String.format("%,d", image.getSize()) + " bytes");
System.out.println("  Format: " + image.getFormat());
System.out.println("  Read-only: " + image.isReadOnly());
System.out.println();
}
return 0;
}
}
@Command(name = "metrics", description = "Metrics and monitoring")
static class MetricsCommand implements Callable<Integer> {
@Parameters(index = "0", description = "VM ID")
private String vmId;
@Option(names = {"--interval", "-i"}, 
description = "Polling interval in seconds",
defaultValue = "5")
private int interval;
@Override
public Integer call() throws Exception {
MicroVMManager vmManager = new MicroVMManager();
MicroVMManager.MicroVMInstance instance = vmManager.getVM(vmId);
if (instance == null) {
System.err.println("VM not found: " + vmId);
return 1;
}
MeterRegistry registry = new SimpleMeterRegistry();
MicroVMMetricsCollector metricsCollector = 
new MicroVMMetricsCollector(registry);
metricsCollector.startMonitoring(
vmId, 
instance.getMetricsPath(), 
instance.getApiClient()
);
System.out.println("Monitoring VM: " + vmId);
System.out.println("Press Ctrl+C to stop...");
// Keep running until interrupted
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
metricsCollector.stopMonitoring(vmId);
metricsCollector.shutdown();
return 0;
}
}
public static void main(String[] args) {
int exitCode = new CommandLine(new FirecrackerCLI())
.addSubcommand("create", new CreateCommand())
.addSubcommand("start", new StartCommand())
.addSubcommand("stop", new StopCommand())
.addSubcommand("list", new ListCommand())
.addSubcommand("info", new InfoCommand())
.addSubcommand("snapshot", new SnapshotCommand())
.addSubcommand("network", new NetworkCommand())
.addSubcommand("disk", new DiskCommand())
.addSubcommand("metrics", new MetricsCommand())
.execute(args);
System.exit(exitCode);
}
}

Example Usage

package com.firecracker.examples;
import com.firecracker.vm.*;
import com.firecracker.disk.MicroVMDiskManager;
import com.firecracker.network.MicroVMNetworkManager;
import java.nio.file.Paths;
public class FirecrackerExample {
public static void main(String[] args) throws Exception {
// Example 1: Create a simple MicroVM
createSimpleVM();
// Example 2: Create VM with network
createVMWithNetwork();
// Example 3: Snapshot and restore
snapshotExample();
}
private static void createSimpleVM() throws Exception {
System.out.println("=== Example 1: Creating Simple MicroVM ===");
MicroVMManager vmManager = new MicroVMManager();
// Create VM configuration
MicroVMConfig config = new MicroVMConfig();
// Configure boot source
MicroVMConfig.BootSource bootSource = new MicroVMConfig.BootSource();
bootSource.setKernelImagePath("/path/to/vmlinux");
bootSource.setBootArgs("console=ttyS0 reboot=k panic=1 pci=off");
config.setBootSource(bootSource);
// Configure root drive
MicroVMConfig.Drive rootDrive = new MicroVMConfig.Drive();
rootDrive.setDriveId("rootfs");
rootDrive.setPathOnHost("/path/to/rootfs.ext4");
rootDrive.setRootDevice(true);
rootDrive.setReadOnly(false);
config.setDrives(java.util.Arrays.asList(rootDrive));
// Configure machine
MicroVMConfig.MachineConfig machineConfig = new MicroVMConfig.MachineConfig();
machineConfig.setVcpuCount(2);
machineConfig.setMemSizeMib(256);
config.setMachineConfig(machineConfig);
// Create VM
MicroVMManager.MicroVMInstance vm = vmManager.createVM("example-vm-1", config);
System.out.println("Created VM: " + vm.getId());
System.out.println("Socket: " + vm.getSocketPath());
// Start VM
vmManager.startVM("example-vm-1").get();
System.out.println("VM started successfully");
// Get VM info
java.util.Map<String, Object> info = vm.getApiClient().getInstanceInfo().get();
System.out.println("VM Info: " + info);
// Stop VM
vmManager.stopVM("example-vm-1").get();
System.out.println("VM stopped");
vmManager.shutdown();
}
private static void createVMWithNetwork() throws Exception {
System.out.println("\n=== Example 2: Creating VM with Network ===");
// Create network interface
MicroVMNetworkManager networkManager = new MicroVMNetworkManager();
MicroVMNetworkManager.TapInterface tap = 
networkManager.createTapInterface("net-vm-1", "02:00:00:00:00:01");
System.out.println("Created TAP interface: " + tap.getName());
// Create VM with network
MicroVMManager vmManager = new MicroVMManager();
MicroVMConfig config = new MicroVMConfig();
// Configure boot source
MicroVMConfig.BootSource bootSource = new MicroVMConfig.BootSource();
bootSource.setKernelImagePath("/path/to/vmlinux");
bootSource.setBootArgs("console=ttyS0 reboot=k panic=1 pci=off");
config.setBootSource(bootSource);
// Configure root drive
MicroVMConfig.Drive rootDrive = new MicroVMConfig.Drive();
rootDrive.setDriveId("rootfs");
rootDrive.setPathOnHost("/path/to/rootfs.ext4");
rootDrive.setRootDevice(true);
rootDrive.setReadOnly(false);
config.setDrives(java.util.Arrays.asList(rootDrive));
// Configure network interface
MicroVMConfig.NetworkInterface netIface = new MicroVMConfig.NetworkInterface();
netIface.setIfaceId("eth0");
netIface.setGuestMac(tap.getMacAddress());
netIface.setHostDevName(tap.getName());
config.setNetworkInterfaces(java.util.Arrays.asList(netIface));
// Configure machine
MicroVMConfig.MachineConfig machineConfig = new MicroVMConfig.MachineConfig();
machineConfig.setVcpuCount(1);
machineConfig.setMemSizeMib(128);
config.setMachineConfig(machineConfig);
// Create VM
MicroVMManager.MicroVMInstance vm = vmManager.createVM("net-vm-1", config);
// Start VM
vmManager.startVM("net-vm-1").get();
System.out.println("VM with network started");
// Configure firewall rules
networkManager.configureFirewallRules("net-vm-1", tap.getName());
System.out.println("Firewall rules configured");
// Stop VM
vmManager.stopVM("net-vm-1").get();
// Cleanup network
networkManager.removeTapInterface(tap.getName());
networkManager.removeFirewallRules("net-vm-1", tap.getName());
vmManager.shutdown();
networkManager.shutdown();
}
private static void snapshotExample() throws Exception {
System.out.println("\n=== Example 3: Snapshot and Restore ===");
MicroVMManager vmManager = new MicroVMManager();
// Create a VM
MicroVMConfig config = createSimpleConfig();
MicroVMManager.MicroVMInstance vm = vmManager.createVM("snapshot-vm", config);
vmManager.startVM("snapshot-vm").get();
System.out.println("VM started for snapshot");
// Wait a bit for VM to initialize
Thread.sleep(2000);
// Create snapshot
SnapshotConfig snapshotConfig = new SnapshotConfig();
snapshotConfig.setSnapshotPath("/tmp/snapshot-vm.snapshot");
snapshotConfig.setMemFilePath("/tmp/snapshot-vm.mem");
vmManager.createSnapshot("snapshot-vm", snapshotConfig).get();
System.out.println("Snapshot created");
// Stop original VM
vmManager.stopVM("snapshot-vm").get();
// Load from snapshot
MicroVMManager.MicroVMInstance restoredVm = 
vmManager.loadSnapshot("restored-vm", snapshotConfig);
System.out.println("VM restored from snapshot: " + restoredVm.getId());
// Stop restored VM
vmManager.forceStopVM("restored-vm").get();
vmManager.shutdown();
}
private static MicroVMConfig createSimpleConfig() {
MicroVMConfig config = new MicroVMConfig();
MicroVMConfig.BootSource bootSource = new MicroVMConfig.BootSource();
bootSource.setKernelImagePath("/path/to/vmlinux");
bootSource.setBootArgs("console=ttyS0 reboot=k panic=1 pci=off");
config.setBootSource(bootSource);
MicroVMConfig.Drive rootDrive = new MicroVMConfig.Drive();
rootDrive.setDriveId("rootfs");
rootDrive.setPathOnHost("/path/to/rootfs.ext4");
rootDrive.setRootDevice(true);
config.setDrives(java.util.Arrays.asList(rootDrive));
MicroVMConfig.MachineConfig machineConfig = new MicroVMConfig.MachineConfig();
machineConfig.setVcpuCount(1);
machineConfig.setMemSizeMib(128);
config.setMachineConfig(machineConfig);
return config;
}
}

Testing

package com.firecracker.test;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.io.TempDir;
import com.firecracker.vm.*;
import com.firecracker.api.FirecrackerClient;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*;
class FirecrackerTest {
@TempDir
Path tempDir;
@Test
void testMicroVMConfigSerialization() throws Exception {
MicroVMConfig config = new MicroVMConfig();
MicroVMConfig.BootSource bootSource = new MicroVMConfig.BootSource();
bootSource.setKernelImagePath("/vmlinux");
bootSource.setBootArgs("console=ttyS0");
config.setBootSource(bootSource);
MicroVMConfig.MachineConfig machineConfig = new MicroVMConfig.MachineConfig();
machineConfig.setVcpuCount(2);
machineConfig.setMemSizeMib(256);
config.setMachineConfig(machineConfig);
com.fasterxml.jackson.databind.ObjectMapper mapper = 
new com.fasterxml.jackson.databind.ObjectMapper();
String json = mapper.writeValueAsString(config);
assertNotNull(json);
assertTrue(json.contains("vmlinux"));
assertTrue(json.contains("vcpu_count"));
}
@Test
void testMicroVMManagerLifecycle() throws Exception {
MicroVMManager vmManager = new MicroVMManager(tempDir);
// Create simple config
MicroVMConfig config = new MicroVMConfig();
MicroVMConfig.BootSource bootSource = new MicroVMConfig.BootSource();
bootSource.setKernelImagePath("/dev/null"); // Placeholder
config.setBootSource(bootSource);
// This would fail without actual Firecracker binary
// For now, just test that manager initializes
assertNotNull(vmManager);
vmManager.shutdown();
}
@Test
void testFirecrackerClient() {
// Test client creation
FirecrackerClient client = new FirecrackerClient("/tmp/test.socket");
assertNotNull(client);
// Note: Actual API calls would require running Firecracker
client.shutdown();
}
}

Conclusion

This comprehensive Firecracker MicroVM implementation in Java provides:

  1. Complete MicroVM Management - Full lifecycle management of Firecracker instances
  2. Network Management - TAP interface creation, bridging, and firewall configuration
  3. Disk Management - Disk image creation, formatting, and management
  4. Metrics Collection - Real-time monitoring of VM performance
  5. Snapshot Support - Create and restore VM snapshots
  6. Security - Jailer integration for enhanced isolation
  7. CLI Interface - Comprehensive command-line tool
  8. API Client - Complete Firecracker API implementation

Key features:

  • Lightweight - Minimal overhead microVMs
  • Secure - KVM-based isolation with minimal attack surface
  • Fast - Sub-second startup times
  • Efficient - Low memory footprint
  • Scalable - Can run thousands of microVMs per host

This implementation can be extended with:

  • Kubernetes Integration - CRI-O or containerd shim
  • Multi-tenancy - Resource isolation and QoS
  • Live Migration - Move VMs between hosts
  • GPU Passthrough - Accelerated workloads
  • Advanced Networking - SR-IOV, DPDK integration
  • Monitoring Dashboard - Web UI for management

The Firecracker Java SDK enables running lightweight, secure microVMs for serverless computing, containers, CI/CD environments, and other workloads requiring strong isolation and fast startup times.

Title: Advanced Java Security: OAuth 2.0, Strong Authentication & Cryptographic Best Practices

Summary: These articles collectively explain how modern Java systems implement secure authentication, authorization, and password protection using industry-grade standards like OAuth 2.0 extensions, mutual TLS, and advanced password hashing algorithms, along with cryptographic best practices for generating secure randomness.

Links with explanations:
https://macronepal.com/blog/dpop-oauth-demonstrating-proof-of-possession-in-java-binding-tokens-to-clients/ (Explains DPoP, which binds OAuth tokens to a specific client so stolen tokens cannot be reused by attackers)
https://macronepal.com/blog/beyond-bearer-tokens-implementing-mutual-tls-for-strong-authentication-in-java/ (Covers mTLS, where both client and server authenticate each other using certificates for stronger security than bearer tokens)
https://macronepal.com/blog/oauth-2-0-token-exchange-in-java-implementing-rfc-8693-for-modern-identity-flows/ (Explains token exchange, allowing secure swapping of access tokens between services in distributed systems)
https://macronepal.com/blog/true-randomness-integrating-hardware-rngs-for-cryptographically-secure-java-applications/ (Discusses hardware-based random number generation for producing truly secure cryptographic keys)
https://macronepal.com/blog/the-password-hashing-dilemma-bcrypt-vs-pbkdf2-in-java/ (Compares BCrypt and PBKDF2 for password hashing and their resistance to brute-force attacks)
https://macronepal.com/blog/scrypt-implementation-in-java-memory-hard-password-hashing-for-jvm-applications/ (Explains Scrypt, a memory-hard hashing algorithm designed to resist GPU/ASIC attacks)
https://macronepal.com/blog/modern-password-security-implementing-argon2-in-java-applications/ (Covers Argon2, a modern and highly secure password hashing algorithm with strong memory-hard protections)

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper