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//! **Note:** This node only supports Windows. Callers should check the platform
14//! before requesting this node.
15//!
16//! See also: stop_test_igvm_agent_rpc_server for cleanup after tests complete.
17
18use flowey::node::prelude::*;
19use std::collections::BTreeMap;
20
21flowey_request! {
22    pub struct Request {
23        /// Environment variables from init_vmm_tests_env (contains VMM_TESTS_CONTENT_DIR and TEST_OUTPUT_PATH)
24        pub env: ReadVar<BTreeMap<String, String>>,
25        /// Completion indicator - signals that the server is ready
26        pub done: WriteVar<SideEffect>,
27    }
28}
29
30new_simple_flow_node!(struct Node);
31
32impl SimpleFlowNode for Node {
33    type Request = Request;
34
35    fn imports(_ctx: &mut ImportCtx<'_>) {}
36
37    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
38        let Request { env, done } = request;
39
40        // This node only supports Windows - fail at flow-graph construction time
41        // if someone mistakenly tries to use it on another platform.
42        if !matches!(ctx.platform(), FlowPlatform::Windows) {
43            anyhow::bail!(
44                "run_test_igvm_agent_rpc_server only supports Windows. \
45                Callers should check the platform before requesting this node."
46            );
47        }
48
49        ctx.emit_rust_step("starting test_igvm_agent_rpc_server", |ctx| {
50            let env = env.claim(ctx);
51            done.claim(ctx);
52            move |rt| start_rpc_server(rt.read(env))
53        });
54
55        Ok(())
56    }
57}
58
59#[cfg(windows)]
60fn start_rpc_server(env: BTreeMap<String, String>) -> anyhow::Result<()> {
61    use std::os::windows::process::CommandExt;
62    use std::path::Path;
63
64    let test_content_dir = env
65        .get("VMM_TESTS_CONTENT_DIR")
66        .context("VMM_TESTS_CONTENT_DIR not set")?;
67    let test_output_path = env
68        .get("TEST_OUTPUT_PATH")
69        .context("TEST_OUTPUT_PATH not set")?;
70
71    let exe = Path::new(test_content_dir).join("test_igvm_agent_rpc_server.exe");
72
73    if !exe.exists() {
74        log::info!(
75            "test_igvm_agent_rpc_server.exe not found at {}, skipping",
76            exe.display()
77        );
78        return Ok(());
79    }
80
81    // Create log file for server output
82    let log_file_path = Path::new(test_output_path).join("test_igvm_agent_rpc_server.log");
83    let log_file = std::fs::File::create(&log_file_path)?;
84    let log_file_stderr = log_file.try_clone()?;
85
86    log::info!(
87        "starting test_igvm_agent_rpc_server from {}, logs at: {}",
88        exe.display(),
89        log_file_path.display()
90    );
91
92    // Spawn the RPC server as a background process.
93    // Use CREATE_NEW_PROCESS_GROUP so it doesn't receive console signals.
94    const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
95
96    let mut child = std::process::Command::new(&exe)
97        .stdin(std::process::Stdio::null())
98        .stdout(log_file)
99        .stderr(log_file_stderr)
100        .creation_flags(CREATE_NEW_PROCESS_GROUP)
101        .spawn()
102        .with_context(|| {
103            format!(
104                "failed to spawn test_igvm_agent_rpc_server: {}",
105                exe.display()
106            )
107        })?;
108
109    // Give the server a moment to start up and bind to the RPC endpoint.
110    std::thread::sleep(std::time::Duration::from_millis(500));
111
112    // Check if the server is still running
113    match child.try_wait()? {
114        Some(status) => {
115            anyhow::bail!(
116                "test_igvm_agent_rpc_server exited unexpectedly with status: {:?}. \
117                Check logs at: {}",
118                status.code(),
119                log_file_path.display()
120            );
121        }
122        None => {
123            log::info!(
124                "test_igvm_agent_rpc_server started successfully (pid: {})",
125                child.id()
126            );
127        }
128    }
129
130    // Don't wait on the child - let it run in the background.
131    // The process will be cleaned up by stop_test_igvm_agent_rpc_server
132    // after tests complete. We intentionally drop the Child handle.
133    drop(child);
134
135    Ok(())
136}
137
138#[cfg(not(windows))]
139fn start_rpc_server(_env: BTreeMap<String, String>) -> anyhow::Result<()> {
140    // This should never be called - the node rejects non-Windows at construction time.
141    // But we need this for compilation on non-Windows hosts.
142    anyhow::bail!("run_test_igvm_agent_rpc_server is only supported on Windows")
143}