Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ Enumerating a function's input variants as grouped `carbide-test-support` scenar
or explicit cases (`check_cases` / `check_values`) is the easiest way to reach thorough coverage of parsers, validators,
conversions, and the like.

Keep test rack-profile capability counts aligned with the inventory the fixture
actually instantiates. Use zero for unsupported component types so tests do not
generate expected-but-absent discovery errors.

### Linting and Formatting

```bash
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/api-integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ authors.workspace = true
# [local-dependencies]
# DO NOT PUT DEPENDENCIES OTHER THAN LOCAL DEPS HERE, THEY SHOULD ALL HAVE 'path =' IN THEM.
carbide-machine-a-tron = { path = "../machine-a-tron" }
carbide-uuid = { path = "../uuid" }
carbide-utils = { path = "../utils", features = ["sqlx"] }
carbide-api-test-helper = { path = "../api-test-helper" }
carbide-kms-provider = { path = "../kms-provider" }
Expand Down
146 changes: 145 additions & 1 deletion crates/api-integration-tests/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ use std::sync::Arc;
use std::time::{self, Duration};

use ::carbide_utils::HostPortPair;
use ::machine_a_tron::{BmcMockRegistry, HostMachineHandle, MachineATronConfig, MachineConfig};
use ::machine_a_tron::{
BmcMockRegistry, HostMachineHandle, MachineATronConfig, MachineConfig, RackConfig,
};
use api_test_helper::{
IntegrationTestEnvironment, domain, instance, machine, metrics, subnet, tenant, utils, vpc,
vpc_prefix,
};
use bmc_mock::test_support::TEST_MAC_POOL;
use bmc_mock::{HostHardwareType, ListenerOrAddress};
use carbide_uuid::rack::{RackId, RackProfileId};
use eyre::ContextCompat;
use futures::FutureExt;
use futures::future::join_all;
Expand Down Expand Up @@ -255,6 +258,58 @@ async fn test_integration() -> eyre::Result<()> {
Ok(())
}

/// Exercise the rack-aware machine-a-tron path independently from the other parallel scenarios.
#[tokio::test(flavor = "multi_thread")]
async fn test_machine_a_tron_rack_integration() -> eyre::Result<()> {
let Some(test_env) = IntegrationTestEnvironment::try_from_environment(
1,
"api_server_test_machine_a_tron_rack_integration",
)
.await?
else {
return Ok(());
};

let bmc_address_registry = BmcMockRegistry::default();
let certs_dir = test_env.root_dir.join("crates/bmc-mock");
let server_config = bmc_mock::tls::server_config(Some(certs_dir)).unwrap();
let mut bmc_mock_handle = bmc_mock::CombinedServer::run(
"bmc-mock",
bmc_address_registry.clone(),
Some(ListenerOrAddress::Listener(TcpListener::bind(
"127.0.0.1:0",
)?)),
server_config,
);
let empty_firmware_dir = temp_dir::TempDir::with_prefix("firmware")?;
let cancel_token = CancellationToken::new();
let server_handle = utils::start_api_server(
test_env.clone(),
Some(HostPortPair::HostAndPort(
"127.0.0.1".to_string(),
bmc_mock_handle.address.port(),
)),
empty_firmware_dir.path().to_owned(),
0,
true,
cancel_token.clone(),
)
.await?;

test_machine_a_tron_rack(
&test_env,
&bmc_address_registry,
Ipv4Addr::new(172, 20, 0, 2),
)
.await?;

cancel_token.cancel();
server_handle.wait().await?;
test_env.db_pool.close().await;
bmc_mock_handle.stop().await?;
Ok(())
}

fn generate_core_metric_docs(metrics_endpoints: &[SocketAddr]) {
let mut infos = metrics::collect_metric_infos(metrics_endpoints).unwrap();
retain_existing_core_metric_infos(&mut infos);
Expand Down Expand Up @@ -442,6 +497,7 @@ async fn test_metrics_integration() -> eyre::Result<()> {
1,
1,
false,
None,
&test_env,
&bmc_address_registry,
Ipv4Addr::new(172, 20, 0, 1),
Expand Down Expand Up @@ -589,6 +645,7 @@ async fn test_machine_a_tron_multidpu(
1,
2,
false,
None,
test_env,
bmc_mock_registry,
admin_dhcp_relay_address,
Expand Down Expand Up @@ -660,6 +717,71 @@ async fn test_machine_a_tron_multidpu(
.await
}

#[derive(Clone)]
struct TestRackConfig {
rack_id: RackId,
rack_profile_id: RackProfileId,
}

async fn test_machine_a_tron_rack(
test_env: &IntegrationTestEnvironment,
bmc_mock_registry: &BmcMockRegistry,
admin_dhcp_relay_address: Ipv4Addr,
) -> eyre::Result<()> {
let rack_id = RackId::new("machine-a-tron-nvl72");
run_machine_a_tron_test(
HostHardwareType::WiwynnGB200Nvl,
18,
2,
false,
Some(TestRackConfig {
rack_id: rack_id.clone(),
rack_profile_id: RackProfileId::new("NVL72"),
}),
test_env,
bmc_mock_registry,
admin_dhcp_relay_address,
|machine_handle| {
let db_pool = test_env.db_pool.clone();
let rack_id = rack_id.clone();
async move {
machine_handle
.wait_until_machine_up_with_api_state("Ready", Duration::from_secs(240))
.await?;
let machine_id = machine_handle
.observed_machine_id()
.expect("Machine ID should be set if host is ready")
.to_string();

let managed_rack_id: Option<String> =
sqlx::query_scalar("SELECT rack_id FROM machines WHERE id = $1")
.bind(machine_id)
.fetch_one(&db_pool)
.await?;
assert_eq!(managed_rack_id.as_deref(), Some(rack_id.as_str()));

let expected_machine_count: i64 =
sqlx::query_scalar("SELECT COUNT(*) FROM expected_machines WHERE rack_id = $1")
.bind(rack_id.as_str())
.fetch_one(&db_pool)
.await?;
assert_eq!(expected_machine_count, 18);

let rack_profile_id: String = sqlx::query_scalar(
"SELECT rack_profile_id FROM expected_racks WHERE rack_id = $1",
)
.bind(rack_id.as_str())
.fetch_one(&db_pool)
.await?;
assert_eq!(rack_profile_id, "NVL72");

Ok::<(), eyre::Report>(())
}
},
)
.await
}

async fn test_machine_a_tron_zerodpu(
hw_type: HostHardwareType,
test_env: &IntegrationTestEnvironment,
Expand All @@ -671,6 +793,7 @@ async fn test_machine_a_tron_zerodpu(
1,
0,
false,
None,
test_env,
bmc_mock_registry,
admin_dhcp_relay_address,
Expand Down Expand Up @@ -731,6 +854,7 @@ async fn test_machine_a_tron_singledpu_nic_mode(
1,
1,
true,
None,
test_env,
bmc_mock_registry,
admin_dhcp_relay_address,
Expand Down Expand Up @@ -805,6 +929,7 @@ async fn test_machine_a_tron_dpu_to_nic_mode_reregistration(
// Start in DPU mode: the host comes up as a managed-DPU machine, and we
// flip it to NIC mode below.
false,
None,
test_env,
bmc_mock_registry,
admin_dhcp_relay_address,
Expand Down Expand Up @@ -970,6 +1095,7 @@ async fn test_machine_a_tron_dual_stack(
1,
1,
false,
None,
test_env,
bmc_mock_registry,
admin_dhcp_relay_address,
Expand Down Expand Up @@ -1077,6 +1203,7 @@ async fn test_machine_a_tron_dual_stack_l2(
1,
1,
false,
None,
test_env,
bmc_mock_registry,
admin_dhcp_relay_address,
Expand Down Expand Up @@ -1137,6 +1264,7 @@ async fn run_machine_a_tron_test<F, O>(
host_count: u32,
dpu_per_host_count: u32,
dpus_in_nic_mode: bool,
rack: Option<TestRackConfig>,
test_env: &IntegrationTestEnvironment,
bmc_mock_registry: &BmcMockRegistry,
admin_dhcp_relay_address: Ipv4Addr,
Expand All @@ -1155,10 +1283,26 @@ where
.iter()
.map(|a| format!("https://{}:{}", a.ip(), a.port()))
.collect();
let (racks, rack_id) = rack
.map(|rack| {
let rack_id = rack.rack_id;
(
BTreeMap::from([(
rack_id.clone(),
RackConfig {
rack_profile_id: rack.rack_profile_id,
},
)]),
Some(rack_id),
)
})
.unwrap_or_default();
let mat_config = MachineATronConfig {
racks,
machines: BTreeMap::from([(
"config".to_string(),
Arc::new(MachineConfig {
rack_id,
hw_type,
host_count,
dpu_per_host_count,
Expand Down
14 changes: 14 additions & 0 deletions crates/api-test-helper/src/api_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,20 @@ pub async fn start(

[host_models]

[rack_profiles.NVL72]
product_family = "gb200"

[rack_profiles.NVL72.rack_capabilities.compute]
name = "GB200"
count = 18
vendor = "NVIDIA"

[rack_profiles.NVL72.rack_capabilities.switch]
count = 0

[rack_profiles.NVL72.rack_capabilities.power_shelf]
count = 0

[firmware_global]
autoupdate = true
host_enable_autoupdate = []
Expand Down
2 changes: 2 additions & 0 deletions crates/api-test-helper/src/machine_a_tron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub async fn run_local(
bmc_address_registry: Option<BmcMockRegistry>,
mac_address_pool: Arc<Mutex<MacAddressPool>>,
) -> eyre::Result<(Vec<HostMachineHandle>, MachineATronHandle)> {
app_config.validate()?;

let forge_root_ca_path = get_root_ca_path(None, None); // Will get it from the local repo
let forge_client_config = ForgeClientConfig::new(forge_root_ca_path.clone(), None);

Expand Down
1 change: 1 addition & 0 deletions crates/machine-a-tron/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ carbide-network = { path = "../network" }
carbide-utils = { path = "../utils", features = ["test-support"] }

[dev-dependencies]
carbide-test-support = { path = "../test-support" }
toml = { workspace = true }

[build-dependencies]
Expand Down
7 changes: 7 additions & 0 deletions crates/machine-a-tron/config/mat.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,14 @@ bmc_mock_port = 2000
# host_bmc_password = "vault-password"
# dpu_bmc_password = "vault-password"

# Optional rack declarations. Machine groups can reference these declarations
# with rack_id to register rack-aware expected inventory.
#
# [racks.rack-001]
# rack_profile_id = "NVL72"

[machines.config]
# rack_id = "rack-001"
host_count = 3
vpc_count = 10
dpu_per_host_count = 1
Expand Down
Loading
Loading