1#![deny(
17 clippy::unwrap_used,
18 clippy::expect_used,
19 clippy::indexing_slicing,
20 clippy::panic
21)]
22
23use std::sync::Arc;
24
25use axum::{
26 Json,
27 extract::{Path, State},
28 http::{StatusCode, Uri},
29 response::{IntoResponse, Response},
30};
31use serde_json::json;
32
33use crate::{
34 action::{ApiAction, ApiResponse},
35 controller::RuntimeApiController,
36 error::{ApiError, Result},
37 schemas::{
38 BalloonConfig, BalloonHintingOp, BalloonStatsUpdate, BalloonUpdate, BootSourceConfig,
39 CpuConfig, DriveConfig, DriveId, DrivePatch, EntropyConfig, HotplugMemoryConfig,
40 HotplugMemoryUpdate, IfaceId, InstanceActionInfo, InstanceInfo, LoggerConfig,
41 MachineConfig, MachineConfigPatch, MetricsConfig, MmdsConfig, MmdsContents,
42 NetworkInterfaceConfig, NetworkPatch, PmemConfig, PmemPatch, SerialConfig,
43 SnapshotCreateConfig, SnapshotLoadConfig, VersionResponse, VmStateChange, VsockConfig,
44 actions::RawInstanceActionInfo,
45 balloon::{RawBalloonConfig, RawBalloonStatsUpdate, RawBalloonUpdate},
46 boot_source::RawBootSourceConfig,
47 cpu_config::RawCpuConfig,
48 drive::{RawDriveConfig, RawDrivePatch},
49 entropy::RawEntropyConfig,
50 hotplug_memory::{RawHotplugMemoryConfig, RawHotplugMemoryUpdate},
51 logger::RawLoggerConfig,
52 machine_config::{RawMachineConfig, RawMachineConfigPatch},
53 metrics::RawMetricsConfig,
54 mmds::RawMmdsConfig,
55 network::{RawNetworkInterfaceConfig, RawNetworkPatch},
56 pmem::{RawPmemConfig, RawPmemPatch},
57 serial::RawSerialConfig,
58 snapshot::{RawSnapshotCreateConfig, RawSnapshotLoadConfig},
59 vm::RawVmPatch,
60 vsock::RawVsockConfig,
61 },
62};
63
64fn bad(s: String) -> ApiError {
69 ApiError::BadRequest(s)
70}
71
72fn finish(resp: ApiResponse) -> Response {
74 match resp {
75 ApiResponse::NoContent => StatusCode::NO_CONTENT.into_response(),
76 ApiResponse::Json(v) => (StatusCode::OK, Json(v)).into_response(),
77 ApiResponse::Fault {
78 status,
79 fault_message,
80 } => {
81 let status = StatusCode::from_u16(status).unwrap_or(StatusCode::BAD_REQUEST);
82 (status, Json(crate::error::FaultMessage::new(fault_message))).into_response()
83 }
84 }
85}
86
87pub async fn get_root(
91 State(controller): State<Arc<RuntimeApiController>>,
92) -> Result<Json<InstanceInfo>> {
93 Ok(Json(controller.snapshot().instance_info.clone()))
94}
95
96pub async fn get_version(
98 State(controller): State<Arc<RuntimeApiController>>,
99) -> Result<Json<VersionResponse>> {
100 Ok(Json(VersionResponse {
101 firecracker_version: controller.snapshot().firecracker_version.clone(),
102 }))
103}
104
105pub async fn get_vm_config(
107 State(controller): State<Arc<RuntimeApiController>>,
108) -> Result<Json<serde_json::Value>> {
109 Ok(Json((*controller.snapshot().vm_config).clone()))
110}
111
112pub async fn get_machine_config(
115 State(controller): State<Arc<RuntimeApiController>>,
116) -> Result<Json<serde_json::Value>> {
117 let snap = controller.snapshot();
118 let cfg = snap
119 .vm_config
120 .get("machine-config")
121 .cloned()
122 .unwrap_or_else(|| json!({}));
123 Ok(Json(cfg))
124}
125
126pub async fn get_balloon(
128 State(controller): State<Arc<RuntimeApiController>>,
129) -> Result<Json<serde_json::Value>> {
130 let snap = controller.snapshot();
131 let cfg = snap
132 .vm_config
133 .get("balloon")
134 .cloned()
135 .unwrap_or_else(|| json!({}));
136 Ok(Json(cfg))
137}
138
139pub async fn get_balloon_statistics(
141 State(controller): State<Arc<RuntimeApiController>>,
142) -> Result<Json<serde_json::Value>> {
143 let snap = controller.snapshot();
144 let stats = snap
145 .vm_config
146 .get("balloon_statistics")
147 .cloned()
148 .unwrap_or_else(|| json!({}));
149 Ok(Json(stats))
150}
151
152pub async fn get_mmds(
154 State(controller): State<Arc<RuntimeApiController>>,
155) -> Result<Json<serde_json::Value>> {
156 let snap = controller.snapshot();
157 let tree = snap
158 .vm_config
159 .get("mmds")
160 .cloned()
161 .unwrap_or(serde_json::Value::Null);
162 Ok(Json(tree))
163}
164
165pub async fn put_boot_source(
169 State(controller): State<Arc<RuntimeApiController>>,
170 Json(raw): Json<RawBootSourceConfig>,
171) -> Result<Response> {
172 let cfg = BootSourceConfig::try_from(raw).map_err(bad)?;
173 let resp = controller.dispatch(ApiAction::PutBootSource(cfg)).await?;
174 Ok(finish(resp))
175}
176
177pub async fn put_machine_config(
179 State(controller): State<Arc<RuntimeApiController>>,
180 Json(raw): Json<RawMachineConfig>,
181) -> Result<Response> {
182 let cfg = MachineConfig::try_from(raw).map_err(bad)?;
183 let resp = controller
184 .dispatch(ApiAction::PutMachineConfig(cfg))
185 .await?;
186 Ok(finish(resp))
187}
188
189pub async fn patch_machine_config(
191 State(controller): State<Arc<RuntimeApiController>>,
192 Json(raw): Json<RawMachineConfigPatch>,
193) -> Result<Response> {
194 let patch = MachineConfigPatch::try_from(raw).map_err(bad)?;
195 let resp = controller
196 .dispatch(ApiAction::PatchMachineConfig(patch))
197 .await?;
198 Ok(finish(resp))
199}
200
201pub async fn put_drive(
203 State(controller): State<Arc<RuntimeApiController>>,
204 Path(id): Path<String>,
205 Json(raw): Json<RawDriveConfig>,
206) -> Result<Response> {
207 if raw.drive_id != id {
208 return Err(ApiError::BadRequest(
209 "Invalid drive: URL id and body drive_id must match".into(),
210 ));
211 }
212 let cfg = DriveConfig::try_from(raw).map_err(bad)?;
213 let resp = controller.dispatch(ApiAction::PutDrive(cfg)).await?;
214 Ok(finish(resp))
215}
216
217pub async fn patch_drive(
219 State(controller): State<Arc<RuntimeApiController>>,
220 Path(id): Path<String>,
221 Json(raw): Json<RawDrivePatch>,
222) -> Result<Response> {
223 if raw.drive_id != id {
224 return Err(ApiError::BadRequest(
225 "Invalid drive: URL id and body drive_id must match".into(),
226 ));
227 }
228 let patch = DrivePatch::try_from(raw).map_err(bad)?;
229 let resp = controller.dispatch(ApiAction::PatchDrive(patch)).await?;
230 Ok(finish(resp))
231}
232
233pub async fn delete_drive(
235 State(controller): State<Arc<RuntimeApiController>>,
236 Path(id): Path<String>,
237) -> Result<Response> {
238 let drive_id = DriveId::new(id).map_err(bad)?;
239 let resp = controller
240 .dispatch(ApiAction::DeleteDrive { drive_id })
241 .await?;
242 Ok(finish(resp))
243}
244
245pub async fn put_network(
247 State(controller): State<Arc<RuntimeApiController>>,
248 Path(id): Path<String>,
249 Json(raw): Json<RawNetworkInterfaceConfig>,
250) -> Result<Response> {
251 if raw.iface_id != id {
252 return Err(ApiError::BadRequest(
253 "Invalid network-interface: URL id and body iface_id must match".into(),
254 ));
255 }
256 let cfg = NetworkInterfaceConfig::try_from(raw).map_err(bad)?;
257 let resp = controller.dispatch(ApiAction::PutNetwork(cfg)).await?;
258 Ok(finish(resp))
259}
260
261pub async fn patch_network(
263 State(controller): State<Arc<RuntimeApiController>>,
264 Path(id): Path<String>,
265 Json(raw): Json<RawNetworkPatch>,
266) -> Result<Response> {
267 if raw.iface_id != id {
268 return Err(ApiError::BadRequest(
269 "Invalid network-interface: URL id and body iface_id must match".into(),
270 ));
271 }
272 let patch = NetworkPatch::try_from(raw).map_err(bad)?;
273 let resp = controller.dispatch(ApiAction::PatchNetwork(patch)).await?;
274 Ok(finish(resp))
275}
276
277pub async fn delete_network(
279 State(controller): State<Arc<RuntimeApiController>>,
280 Path(id): Path<String>,
281) -> Result<Response> {
282 let iface_id = IfaceId::new(id).map_err(bad)?;
283 let resp = controller
284 .dispatch(ApiAction::DeleteNetwork { iface_id })
285 .await?;
286 Ok(finish(resp))
287}
288
289pub async fn put_vsock(
291 State(controller): State<Arc<RuntimeApiController>>,
292 Json(raw): Json<RawVsockConfig>,
293) -> Result<Response> {
294 let cfg = VsockConfig::try_from(raw).map_err(bad)?;
295 let resp = controller.dispatch(ApiAction::PutVsock(cfg)).await?;
296 Ok(finish(resp))
297}
298
299pub async fn put_mmds(
301 State(controller): State<Arc<RuntimeApiController>>,
302 Json(value): Json<serde_json::Value>,
303) -> Result<Response> {
304 let resp = controller
305 .dispatch(ApiAction::PutMmds(MmdsContents::new(value)))
306 .await?;
307 Ok(finish(resp))
308}
309
310pub async fn patch_mmds(
312 State(controller): State<Arc<RuntimeApiController>>,
313 Json(value): Json<serde_json::Value>,
314) -> Result<Response> {
315 let resp = controller
316 .dispatch(ApiAction::PatchMmds(MmdsContents::new(value)))
317 .await?;
318 Ok(finish(resp))
319}
320
321pub async fn put_mmds_config(
323 State(controller): State<Arc<RuntimeApiController>>,
324 Json(raw): Json<RawMmdsConfig>,
325) -> Result<Response> {
326 let cfg = MmdsConfig::try_from(raw).map_err(bad)?;
327 let resp = controller.dispatch(ApiAction::PutMmdsConfig(cfg)).await?;
328 Ok(finish(resp))
329}
330
331pub async fn put_balloon(
333 State(controller): State<Arc<RuntimeApiController>>,
334 Json(raw): Json<RawBalloonConfig>,
335) -> Result<Response> {
336 let cfg = BalloonConfig::try_from(raw).map_err(bad)?;
337 let resp = controller.dispatch(ApiAction::PutBalloon(cfg)).await?;
338 Ok(finish(resp))
339}
340
341pub async fn patch_balloon(
343 State(controller): State<Arc<RuntimeApiController>>,
344 Json(raw): Json<RawBalloonUpdate>,
345) -> Result<Response> {
346 let upd = BalloonUpdate::try_from(raw).map_err(bad)?;
347 let resp = controller.dispatch(ApiAction::PatchBalloon(upd)).await?;
348 Ok(finish(resp))
349}
350
351pub async fn patch_balloon_statistics(
353 State(controller): State<Arc<RuntimeApiController>>,
354 Json(raw): Json<RawBalloonStatsUpdate>,
355) -> Result<Response> {
356 let upd = BalloonStatsUpdate::try_from(raw).map_err(bad)?;
357 let resp = controller
358 .dispatch(ApiAction::PatchBalloonStats(upd))
359 .await?;
360 Ok(finish(resp))
361}
362
363pub async fn patch_balloon_hinting(
368 State(controller): State<Arc<RuntimeApiController>>,
369 Path(op): Path<String>,
370) -> Result<Response> {
371 let op = BalloonHintingOp::from_url_segment(&op).map_err(bad)?;
372 let resp = controller
373 .dispatch(ApiAction::PatchBalloonHinting { op })
374 .await?;
375 Ok(finish(resp))
376}
377
378pub async fn put_entropy(
380 State(controller): State<Arc<RuntimeApiController>>,
381 Json(raw): Json<RawEntropyConfig>,
382) -> Result<Response> {
383 let cfg = EntropyConfig::try_from(raw).map_err(bad)?;
384 let resp = controller.dispatch(ApiAction::PutEntropy(cfg)).await?;
385 Ok(finish(resp))
386}
387
388pub async fn put_serial(
390 State(controller): State<Arc<RuntimeApiController>>,
391 Json(raw): Json<RawSerialConfig>,
392) -> Result<Response> {
393 let cfg = SerialConfig::try_from(raw).map_err(bad)?;
394 let resp = controller.dispatch(ApiAction::PutSerial(cfg)).await?;
395 Ok(finish(resp))
396}
397
398pub async fn put_pmem(
400 State(controller): State<Arc<RuntimeApiController>>,
401 Path(id): Path<String>,
402 Json(raw): Json<RawPmemConfig>,
403) -> Result<Response> {
404 if raw.pmem_id != id {
405 return Err(ApiError::BadRequest(
406 "Invalid pmem: URL id and body pmem_id must match".into(),
407 ));
408 }
409 let cfg = PmemConfig::try_from(raw).map_err(bad)?;
410 let resp = controller.dispatch(ApiAction::PutPmem(cfg)).await?;
411 Ok(finish(resp))
412}
413
414pub async fn patch_pmem(
416 State(controller): State<Arc<RuntimeApiController>>,
417 Path(id): Path<String>,
418 Json(raw): Json<RawPmemPatch>,
419) -> Result<Response> {
420 if raw.pmem_id != id {
421 return Err(ApiError::BadRequest(
422 "Invalid pmem: URL id and body pmem_id must match".into(),
423 ));
424 }
425 let patch = PmemPatch::try_from(raw).map_err(bad)?;
426 let resp = controller.dispatch(ApiAction::PatchPmem(patch)).await?;
427 Ok(finish(resp))
428}
429
430pub async fn delete_pmem(
432 State(controller): State<Arc<RuntimeApiController>>,
433 Path(id): Path<String>,
434) -> Result<Response> {
435 let pmem_id = DriveId::new(id).map_err(bad)?;
436 let resp = controller
437 .dispatch(ApiAction::DeletePmem { pmem_id })
438 .await?;
439 Ok(finish(resp))
440}
441
442pub async fn get_hotplug_memory(
444 State(controller): State<Arc<RuntimeApiController>>,
445) -> Result<Json<serde_json::Value>> {
446 let snap = controller.snapshot();
447 let cfg = snap
448 .vm_config
449 .get("hotplug-memory")
450 .cloned()
451 .unwrap_or_else(|| serde_json::json!({}));
452 Ok(Json(cfg))
453}
454
455pub async fn put_hotplug_memory(
457 State(controller): State<Arc<RuntimeApiController>>,
458 Json(raw): Json<RawHotplugMemoryConfig>,
459) -> Result<Response> {
460 let cfg = HotplugMemoryConfig::try_from(raw).map_err(bad)?;
461 let resp = controller
462 .dispatch(ApiAction::PutHotplugMemory(cfg))
463 .await?;
464 Ok(finish(resp))
465}
466
467pub async fn patch_hotplug_memory(
469 State(controller): State<Arc<RuntimeApiController>>,
470 Json(raw): Json<RawHotplugMemoryUpdate>,
471) -> Result<Response> {
472 let upd = HotplugMemoryUpdate::try_from(raw).map_err(bad)?;
473 let resp = controller
474 .dispatch(ApiAction::PatchHotplugMemory(upd))
475 .await?;
476 Ok(finish(resp))
477}
478
479pub async fn put_cpu_config(
481 State(controller): State<Arc<RuntimeApiController>>,
482 Json(raw): Json<RawCpuConfig>,
483) -> Result<Response> {
484 let cfg = CpuConfig::try_from(raw).map_err(bad)?;
485 let resp = controller.dispatch(ApiAction::PutCpuConfig(cfg)).await?;
486 Ok(finish(resp))
487}
488
489pub async fn put_actions(
491 State(controller): State<Arc<RuntimeApiController>>,
492 Json(raw): Json<RawInstanceActionInfo>,
493) -> Result<Response> {
494 let info = InstanceActionInfo::from(raw);
495 let resp = controller
496 .dispatch(ApiAction::Action(info.action_type))
497 .await?;
498 Ok(finish(resp))
499}
500
501pub async fn patch_vm(
503 State(controller): State<Arc<RuntimeApiController>>,
504 Json(raw): Json<RawVmPatch>,
505) -> Result<Response> {
506 let action = match raw.state {
507 VmStateChange::Paused => ApiAction::PatchVm(VmStateChange::Paused),
508 VmStateChange::Resumed => ApiAction::PatchVm(VmStateChange::Resumed),
509 };
510 let resp = controller.dispatch(action).await?;
511 Ok(finish(resp))
512}
513
514pub async fn put_snapshot_create(
516 State(controller): State<Arc<RuntimeApiController>>,
517 Json(raw): Json<RawSnapshotCreateConfig>,
518) -> Result<Response> {
519 let cfg = SnapshotCreateConfig::try_from(raw).map_err(bad)?;
520 let resp = controller.dispatch(ApiAction::SnapshotCreate(cfg)).await?;
521 Ok(finish(resp))
522}
523
524pub async fn put_snapshot_load(
526 State(controller): State<Arc<RuntimeApiController>>,
527 Json(raw): Json<RawSnapshotLoadConfig>,
528) -> Result<Response> {
529 let cfg = SnapshotLoadConfig::try_from(raw).map_err(bad)?;
530 let resp = controller.dispatch(ApiAction::SnapshotLoad(cfg)).await?;
531 Ok(finish(resp))
532}
533
534pub async fn put_logger(
536 State(controller): State<Arc<RuntimeApiController>>,
537 Json(raw): Json<RawLoggerConfig>,
538) -> Result<Response> {
539 let cfg = LoggerConfig::try_from(raw).map_err(bad)?;
540 let resp = controller.dispatch(ApiAction::PutLogger(cfg)).await?;
541 Ok(finish(resp))
542}
543
544pub async fn put_metrics(
546 State(controller): State<Arc<RuntimeApiController>>,
547 Json(raw): Json<RawMetricsConfig>,
548) -> Result<Response> {
549 let cfg = MetricsConfig::try_from(raw).map_err(bad)?;
550 let resp = controller.dispatch(ApiAction::PutMetrics(cfg)).await?;
551 Ok(finish(resp))
552}
553
554pub async fn fallback(uri: Uri) -> ApiError {
556 ApiError::NotFound(uri.to_string())
557}