Skip to content

Commit b72d2b1

Browse files
committed
tests: add initial tests for shared resources
1 parent 476e01d commit b72d2b1

4 files changed

Lines changed: 170 additions & 11 deletions

File tree

src/test_env/ha.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,20 @@ impl HaEnvironment {
2424
pub fn new_ha(test_id: String, agent_binary_path: &str, manager_binary_path: &str) -> Self {
2525
let ports = get_ports();
2626
let env = TestEnvironment::new("ha", &test_id, agent_binary_path, manager_binary_path);
27-
let config = ha_config(ports, test_id.clone());
27+
let config = ha_config(ports, &test_id);
28+
env.write_out_config(&config);
29+
Self {
30+
env,
31+
test_id,
32+
ports,
33+
config,
34+
}
35+
}
36+
37+
pub fn new_shared(test_id: String, agent_binary_path: &str, manager_binary_path: &str) -> Self {
38+
let ports = get_ports();
39+
let env = TestEnvironment::new("shared", &test_id, agent_binary_path, manager_binary_path);
40+
let config = shared_config(ports, &test_id);
2841
env.write_out_config(&config);
2942
Self {
3043
env,
@@ -146,7 +159,7 @@ impl HaEnvironment {
146159
.unwrap();
147160
}
148161

149-
fn get_status(&self) -> http::ClusterJson {
162+
pub fn get_status(&self) -> http::ClusterJson {
150163
let status = commands::status::get_status(Some(&self.socket_path())).unwrap();
151164
eprintln!("{status:?}");
152165
status
@@ -334,6 +347,16 @@ impl Drop for HaEnvironment {
334347
/// is, started on both hosts in a pair.
335348
fn drop(&mut self) {
336349
for resource in self.config.resources.iter() {
350+
// The following resource types are not exclusive and are allowed to be double-started.
351+
// XXX: Come up with a more robust way to detect this in arbitrary configs rather than
352+
// just hard-coding this list?
353+
if matches!(
354+
resource.kind.as_str(),
355+
"heartbeat/route" | "heartbeat/filesystem" | "heartbeat/export"
356+
) {
357+
continue;
358+
}
359+
337360
if self.env.resource_is_started(resource, 0)
338361
&& self.env.resource_is_started(resource, 1)
339362
{
@@ -344,7 +367,7 @@ impl Drop for HaEnvironment {
344367
}
345368

346369
/// Creates an HA-pair config for use in the ha tests.
347-
fn ha_config(ports: [u16; 2], test_id: String) -> Config {
370+
fn ha_config(ports: [u16; 2], test_id: &str) -> Config {
348371
let mut config = Config {
349372
hosts: Vec::new(),
350373
resources: Vec::new(),
@@ -399,3 +422,41 @@ fn ha_config(ports: [u16; 2], test_id: String) -> Config {
399422

400423
config
401424
}
425+
426+
fn shared_config(ports: [u16; 2], test_id: &str) -> Config {
427+
let path = test_path("configs/nfs.yaml");
428+
let config = std::fs::read_to_string(path).unwrap();
429+
430+
// Use the existing NFS example config...
431+
let mut config: Config = serde_yaml::from_str(&config).unwrap();
432+
// ...but the hosts and resource groups need to be fixed up to reference the specific test
433+
// ports. so strip them out, just keeping the resources.
434+
std::mem::take(&mut config.hosts);
435+
std::mem::take(&mut config.resource_groups);
436+
437+
for (i, port) in ports.iter().enumerate() {
438+
let my_hostname = || -> String { format!("127.0.0.1:{}", port) };
439+
let partner_hostname =
440+
|| -> String { format!("127.0.0.1:{}", if i == 0 { ports[1] } else { ports[0] }) };
441+
442+
let host = config::Host {
443+
hostname: my_hostname(),
444+
fence_agent: Some("fence_test".to_string()),
445+
fence_parameters: Some(HashMap::from([
446+
("target".to_string(), format!("{test_id}_{i}")),
447+
("test_id".to_string(), format!("shared/{test_id}")),
448+
])),
449+
};
450+
451+
let resource_group = config::ResourceGroup {
452+
home_host: my_hostname(),
453+
failover_hosts: vec![partner_hostname()],
454+
root: format!("ip_addr_{i}"),
455+
};
456+
457+
config.hosts.push(host);
458+
config.resource_groups.push(resource_group);
459+
}
460+
461+
config
462+
}

src/test_env/mod.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::{
1111
};
1212

1313
pub mod ha;
14+
pub mod shared;
1415

1516
/// Given a relative `path` in the test directory, prepend the
1617
/// full path to the test directory.
@@ -313,17 +314,25 @@ impl TestEnvironment {
313314
/// Get the path to the "resource state file" used in a test -- that is, the file whose
314315
/// presence indicates the resource is running and whose absence indicates it is stopped.
315316
fn get_resource_path(&self, resource: &config::Resource, agent: usize) -> String {
317+
let fix_path = |path: &String| path.replace("/", "_");
318+
316319
let path = match resource.kind.as_str() {
317-
"heartbeat/ZFS" => &format!("zfs.{}", resource.parameters.get("pool").unwrap()),
318-
"lustre/Lustre" => &format!(
320+
"heartbeat/ZFS" => format!("zfs.{}", resource.parameters.get("pool").unwrap()),
321+
"lustre/Lustre" => format!(
319322
"lustre.{}",
320-
resource
321-
.parameters
322-
.get("mountpoint")
323-
.unwrap()
324-
.replace("/", "_")
323+
fix_path(resource.parameters.get("mountpoint").unwrap())
324+
),
325+
"heartbeat/ip" => format!("ip.{}", resource.parameters.get("ip").unwrap()),
326+
"heartbeat/route" => format!("route.{}", resource.parameters.get("route").unwrap()),
327+
"heartbeat/filesystem" => format!(
328+
"fs.{}",
329+
fix_path(resource.parameters.get("directory").unwrap())
330+
),
331+
"heartbeat/export" => format!(
332+
"export.{}",
333+
fix_path(resource.parameters.get("directory").unwrap())
325334
),
326-
_ => unreachable!(),
335+
_ => todo!("Need to add resource kind '{}' to list.", resource.kind),
327336
};
328337
let path = format!("{}_{agent}.{}", self.test_id, path);
329338
self.private_dir_path.clone() + "/" + &path

src/test_env/shared.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright 2026. Triad National Security, LLC.

tests/shared.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright 2026. Triad National Security, LLC.
3+
4+
#[cfg(test)]
5+
mod tests {
6+
use halo_lib::test_env::ha::*;
7+
8+
/// Create a TestEnvironment for a test.
9+
///
10+
/// The path to the remote binary needs to be determined here and passed into the
11+
/// TestEnvironment constructor because the environment variable is only defined when compiling
12+
/// tests.
13+
fn test_env_helper(test_id: &str) -> HaEnvironment {
14+
HaEnvironment::new_shared(
15+
test_id.to_string(),
16+
env!("CARGO_BIN_EXE_halo_remote"),
17+
env!("CARGO_BIN_EXE_halo_manager"),
18+
)
19+
}
20+
21+
/// Startup, both agents running, all resources stopped.
22+
/// Agents should start resources on their home nodes.
23+
#[test]
24+
fn startup1() {
25+
let env = test_env_helper("startup1");
26+
let _a = env.start_agent(0);
27+
let _b = env.start_agent(1);
28+
let _m = env.start_manager(true);
29+
30+
std::thread::sleep(std::time::Duration::from_secs(1));
31+
32+
let status = env.get_status();
33+
for resource in status.resources {
34+
let (st, _) = resource.single_host_status();
35+
match resource.kind.as_str() {
36+
"heartbeat/ip" => assert_eq!(st, "Running"),
37+
_ => {
38+
for st in resource.status.values() {
39+
assert_eq!(st.status, "Running");
40+
}
41+
}
42+
}
43+
}
44+
}
45+
46+
/// All resources running on one host, after failback, exclusive resources should be running on
47+
/// their home host and shared resources should be running everywhere.
48+
#[test]
49+
fn failback1() {
50+
let env = test_env_helper("failback1");
51+
52+
env.start_resource("ip_addr_0", 0);
53+
env.start_resource("ip_addr_1", 0);
54+
55+
let _a = env.start_agent(0);
56+
let _b = env.start_agent(1);
57+
let _m = env.start_manager(true);
58+
59+
std::thread::sleep(std::time::Duration::from_secs(1));
60+
61+
let status = env.get_status();
62+
for resource in status.resources {
63+
let st0 = resource.status.get(&env.agent_id(0)).unwrap();
64+
assert_eq!(st0.status, "Running");
65+
66+
let st1 = &resource.status.get(&env.agent_id(1)).unwrap().status;
67+
assert!((st1 == "Stopped") | (st1 == "Unknown"));
68+
}
69+
70+
env.failback(1).unwrap();
71+
72+
std::thread::sleep(std::time::Duration::from_secs(1));
73+
74+
let status = env.get_status();
75+
for resource in status.resources {
76+
let (st, _) = resource.single_host_status();
77+
match resource.kind.as_str() {
78+
"heartbeat/ip" => assert_eq!(st, "Running"),
79+
_ => {
80+
for st in resource.status.values() {
81+
assert_eq!(st.status, "Running");
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)