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