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:
- Complete MicroVM Management - Full lifecycle management of Firecracker instances
- Network Management - TAP interface creation, bridging, and firewall configuration
- Disk Management - Disk image creation, formatting, and management
- Metrics Collection - Real-time monitoring of VM performance
- Snapshot Support - Create and restore VM snapshots
- Security - Jailer integration for enhanced isolation
- CLI Interface - Comprehensive command-line tool
- 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)