Repository: firecracker-microvm/firecracker
File: src/firecracker/src/api_server/request/hotplug.rs (Rust)

Code before:
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use vmm::logger::{IncMetric, METRICS};
use vmm::rpc_interface::VmmAction;
use vmm::vmm_config::hotplug::{HotplugRequestConfig, HotplugVcpuConfig};

use super::super::parsed_request::{ParsedRequest, RequestError};
use super::{Body, Method, StatusCode};

pub(crate) fn parse_put_hotplug(
    body: &Body,
    resource_type_from_path: Option<&str>,
) -> Result<ParsedRequest, RequestError> {
    METRICS.put_api_requests.hotplug.inc();
    match resource_type_from_path {
        // Memory hotplugging not implemented yet.
        Some(resource) => match resource {
            "vcpu" => parse_put_vcpu_hotplug(body),
            _ => Err(RequestError::InvalidPathMethod(
                format!("/hotplug/{}", resource),
                Method::Put,
            )),
        },
        None => Err(RequestError::Generic(
            StatusCode::BadRequest,
            "Missing resource type to hotplug.".to_string(),
        )),
    }
}

fn parse_put_vcpu_hotplug(body: &Body) -> Result<ParsedRequest, RequestError> {
    METRICS.hotplug.vcpu_hotplug_request_count.inc();
    let config = serde_json::from_slice::<HotplugVcpuConfig>(body.raw()).map_err(|err| {
        METRICS.hotplug.vcpu_hotplug_request_fails.inc();
        err
    })?;

    Ok(ParsedRequest::new_sync(VmmAction::HotplugRequest(
        HotplugRequestConfig::Vcpu(config),
    )))
}

#[cfg(test)]

Review comment (suggestion):
And now when we have `HotplugRequestConfig` we don't need to look at the `resource_type_from_path`. We can just deserialize the request directly `serde_json::from_slice::<HotplugRequestConfig>(body.raw())` and send it as a `VmmAction`.

Code after:
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use vmm::logger::{IncMetric, METRICS};
use vmm::rpc_interface::VmmAction;
use vmm::vmm_config::hotplug::HotplugRequestConfig;

use super::super::parsed_request::{ParsedRequest, RequestError};
use super::Body;

pub(crate) fn parse_put_hotplug(body: &Body) -> Result<ParsedRequest, RequestError> {
    METRICS.put_api_requests.hotplug.inc();
    METRICS.hotplug.hotplug_request_count.inc();
    let config = serde_json::from_slice::<HotplugRequestConfig>(body.raw()).map_err(|err| {
        METRICS.hotplug.hotplug_request_fails.inc();
        err
    })?;
    Ok(ParsedRequest::new_sync(VmmAction::HotplugRequest(config)))
}

#[cfg(test)]
mod tests {

    use hotplug::parse_put_hotplug;
    use vmm::rpc_interface::VmmAction;
    use vmm::vmm_config::hotplug::{HotplugRequestConfig, HotplugVcpuConfig};

    use super::super::*;
    use crate::api_server::parsed_request::tests::vmm_action_from_request;

    #[test]
    fn test_parse_put_hotplug() {
        // Case 1. Invalid body
        parse_put_hotplug(&Body::new("invalid body")).unwrap_err();

        // Case 2. vCPU Resource
        let body = r#"{
            "Vcpu" : { "add": 4 }
        }"#;

        let expected_config = HotplugVcpuConfig { add: 4 };

        assert_eq!(
            vmm_action_from_request(parse_put_hotplug(&Body::new(body)).unwrap()),