Skip to main content

flowey_lib_hvlite/
run_test_igvm_agent_rpc_server.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Start the test_igvm_agent_rpc_server before running VMM tests.
5//!
6//! The RPC server provides a fake IGVM agent attestation endpoint for
7//! CVM TPM guest tests. It must be running before the tests start and
8//! stay alive for the duration of the test run.
9//!
10//! This node starts the server from the test content directory (where
11//! init_vmm_tests_env copies the binary) and redirects output to a log file.
12//!
13//! This node supports Windows and running Windows binaries through WSL2.
14//!
15//! See also: stop_test_igvm_agent_rpc_server for cleanup after tests complete.
16
17use crate::build_test_igvm_agent_rpc_server::TestIgvmAgentRpcServerOutput;
18use flowey::node::prelude::*;
19use std::collections::BTreeMap;
20
21flowey_request! {
22    pub struct Request {
23        /// IGVM agent binary
24        pub test_igvm_agent_rpc_server: ReadVar<TestIgvmAgentRpcServerOutput>,
25        /// Environment variables from init_vmm_tests_env (contains VMM_TESTS_CONTENT_DIR and TEST_OUTPUT_PATH)
26        pub env: ReadVar<BTreeMap<String, String>>,
27        /// Completion indicator - signals that the server is ready
28        pub done: WriteVar<SideEffect>,
29        /// Used to ensure that the previous test run is complete, if any
30        pub previous_done: Option<ReadVar<SideEffect>>,
31    }
32}
33
34new_simple_flow_node!(struct Node);
35
36impl SimpleFlowNode for Node {
37    type Request = Request;
38
39    fn imports(_ctx: &mut ImportCtx<'_>) {}
40
41    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
42        let Request {
43            test_igvm_agent_rpc_server,
44            env,
45            done,
46            previous_done,
47        } = request;
48
49        ctx.emit_rust_step("starting test_igvm_agent_rpc_server", |ctx| {
50            let test_igvm_agent_rpc_server = test_igvm_agent_rpc_server.claim(ctx);
51            let env = env.claim(ctx);
52            done.claim(ctx);
53            previous_done.claim(ctx);
54            move |rt| start_rpc_server(rt, test_igvm_agent_rpc_server, env)
55        });
56
57        Ok(())
58    }
59}
60
61fn start_rpc_server(
62    rt: &mut RustRuntimeServices<'_>,
63    test_igvm_agent_rpc_server: ReadVar<TestIgvmAgentRpcServerOutput, VarClaimed>,
64    env: ReadVar<BTreeMap<String, String>, VarClaimed>,
65) -> anyhow::Result<()> {
66    use std::path::Path;
67
68    let windows_via_wsl2 = flowey_lib_common::_util::running_in_wsl(rt);
69    if !matches!(rt.platform(), FlowPlatform::Windows) && !windows_via_wsl2 {
70        anyhow::bail!("run_test_igvm_agent_rpc_server only supports Windows or WSL2");
71    }
72
73    let env = rt.read(env);
74
75    let test_output_path = env
76        .get(petri_artifacts_vmm_test::env::TEST_OUTPUT_PATH)
77        .context("TEST_OUTPUT_PATH not set")?;
78
79    let TestIgvmAgentRpcServerOutput { exe, .. } = rt.read(test_igvm_agent_rpc_server);
80
81    // init_vmm_tests_env supplies Windows paths when targeting Windows from WSL2.
82    let test_output_path = if windows_via_wsl2 {
83        flowey_lib_common::_util::wslpath::win_to_linux(rt, test_output_path)?
84    } else {
85        Path::new(test_output_path).to_path_buf()
86    };
87    let log_file_path = test_output_path.join("test_igvm_agent_rpc_server.log");
88    let log_file = std::fs::File::create(&log_file_path)?;
89    let log_file_stderr = log_file.try_clone()?;
90
91    log::info!(
92        "starting test_igvm_agent_rpc_server from {}, logs at: {}",
93        exe.display(),
94        log_file_path.display()
95    );
96
97    let mut command = std::process::Command::new(&exe);
98    command
99        .stdin(std::process::Stdio::null())
100        .stdout(log_file)
101        .stderr(log_file_stderr);
102
103    #[cfg(windows)]
104    {
105        use std::os::windows::process::CommandExt;
106
107        // Use CREATE_NEW_PROCESS_GROUP so it doesn't receive console signals.
108        const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
109        command.creation_flags(CREATE_NEW_PROCESS_GROUP);
110    }
111
112    let mut child = command.spawn().with_context(|| {
113        format!(
114            "failed to spawn test_igvm_agent_rpc_server: {}",
115            exe.display()
116        )
117    })?;
118
119    // Give the server a moment to start up and bind to the RPC endpoint.
120    std::thread::sleep(std::time::Duration::from_millis(500));
121
122    // Check if the server is still running
123    match child.try_wait()? {
124        Some(status) => {
125            anyhow::bail!(
126                "test_igvm_agent_rpc_server exited unexpectedly with status: {:?}. \
127                Check logs at: {}",
128                status.code(),
129                log_file_path.display()
130            );
131        }
132        None => {
133            log::info!(
134                "test_igvm_agent_rpc_server started successfully (pid: {})",
135                child.id()
136            );
137        }
138    }
139
140    // Don't wait on the child - let it run in the background.
141    // The process will be cleaned up by stop_test_igvm_agent_rpc_server
142    // after tests complete. We intentionally drop the Child handle.
143    drop(child);
144
145    Ok(())
146}