Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Current ingestion uses rack membership as the NVLink domain boundary.
-- NVLink Manager stores the last valid domain reported by the rack's NMX-C
-- endpoint on every active switch. NULL means no valid domain was observed.

ALTER TABLE switches
ADD COLUMN nvlink_domain_uuid UUID;
111 changes: 111 additions & 0 deletions crates/api-db/src/switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

use std::net::IpAddr;

use carbide_uuid::nvlink::NvLinkDomainId;
use carbide_uuid::rack::{RackId, RackProfileId};
use carbide_uuid::switch::SwitchId;
use chrono::prelude::*;
Expand Down Expand Up @@ -136,6 +137,7 @@ pub async fn create(txn: &mut PgConnection, new_switch: &NewSwitch) -> DatabaseR
firmware_upgrade_status: None,
nvos_update_status: None,
fabric_manager_status: None,
nvlink_domain_uuid: None,
metadata,
version,
is_primary: false,
Expand Down Expand Up @@ -492,6 +494,32 @@ pub async fn update_fabric_manager_status(
Ok(())
}

/// Records one rack-scoped NVLink domain observation on every active switch.
///
/// Repeated observations are idempotent. Soft-deleted switches retain their
/// previous value. Returns the active switches whose stored value changed.
pub async fn update_nvlink_domain_uuid_for_rack(
txn: &mut PgConnection,
rack_id: &RackId,
nvlink_domain_uuid: NvLinkDomainId,
) -> DatabaseResult<Vec<SwitchId>> {
sqlx::query_scalar(
r#"
UPDATE switches
SET nvlink_domain_uuid = $1
WHERE rack_id = $2
AND deleted IS NULL
AND nvlink_domain_uuid IS DISTINCT FROM $1
RETURNING id
"#,
)
.bind(nvlink_domain_uuid)
.bind(rack_id)
.fetch_all(txn)
.await
.map_err(|error| DatabaseError::new("update_nvlink_domain_uuid_for_rack", error))
}

pub async fn update_slot_and_tray(
txn: &mut PgConnection,
switch_id: &SwitchId,
Expand Down Expand Up @@ -1025,6 +1053,89 @@ mod tests {
Ok(())
}

#[crate::sqlx_test]
async fn rack_domain_update_is_idempotent_and_excludes_deleted_switches(
pool: sqlx::PgPool,
) -> Result<(), Box<dyn std::error::Error>> {
let rack_id = RackId::new("rack-nvlink-domain");
let rack_profile_id = RackProfileId::new("NVL72");
let mut txn = pool.begin().await?;

crate::rack::create(
txn.as_mut(),
&rack_id,
Some(&rack_profile_id),
&RackConfig::default(),
None,
)
.await?;

txn.commit().await?;

for (seed, name) in [
(21, "rack switch 1"),
(22, "rack switch 2"),
(23, "deleted rack switch"),
] {
let mut txn = pool.begin().await?;
let mut switch = create_seeded_discovered(txn.as_mut(), seed, name).await?;
sqlx::query("UPDATE switches SET rack_id = $1 WHERE id = $2")
.bind(&rack_id)
.bind(switch.id)
.execute(txn.as_mut())
.await?;

if seed == 23 {
mark_as_deleted(&mut switch, txn.as_mut()).await?;
}

txn.commit().await?;
}

let first_domain: NvLinkDomainId = "11111111-1111-1111-1111-111111111111".parse()?;
let replacement_domain: NvLinkDomainId = "33333333-3333-3333-3333-333333333333".parse()?;
let mut txn = pool.begin().await?;

for (scenario, domain_uuid, expected_changes) in [
("initial observation", first_domain, 2),
("repeated observation", first_domain, 0),
("replacement observation", replacement_domain, 2),
] {
let changed =
update_nvlink_domain_uuid_for_rack(txn.as_mut(), &rack_id, domain_uuid).await?;

assert_eq!(changed.len(), expected_changes, "{scenario}");
}

txn.commit().await?;

let mut txn = pool.begin().await?;

let counts: (i64, i64) = sqlx::query_as(
r#"
SELECT
COUNT(*) FILTER (
WHERE deleted IS NULL AND nvlink_domain_uuid = $1
),
COUNT(*) FILTER (
WHERE deleted IS NOT NULL AND nvlink_domain_uuid IS NULL
)
FROM switches
WHERE rack_id = $2
"#,
)
.bind(replacement_domain)
.bind(&rack_id)
.fetch_one(txn.as_mut())
.await?;

assert_eq!(counts, (2, 1));

txn.rollback().await?;

Ok(())
}

#[crate::sqlx_test]
async fn test_find_ready_control_plane_configured_switch_ids_in_rack(
pool: sqlx::PgPool,
Expand Down
7 changes: 7 additions & 0 deletions crates/api-model/src/switch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

use std::collections::HashMap;

use carbide_uuid::nvlink::NvLinkDomainId;
use carbide_uuid::rack::RackId;
use carbide_uuid::switch::SwitchId;
use chrono::prelude::*;
Expand Down Expand Up @@ -196,6 +197,10 @@ pub struct Switch {
/// FabricManager / NMX-C status set by the rack state machine.
pub fabric_manager_status: Option<FabricManagerStatus>,

/// Last non-nil NVLink domain observed through the rack's selected NMX-C endpoint.
/// Failed or nil observations leave the previous value unchanged.
pub nvlink_domain_uuid: Option<NvLinkDomainId>,

/// The rack that this switch is associated with.
pub rack_id: Option<RackId>,
// Columns for these exist, but are unused in rust code
Expand Down Expand Up @@ -263,6 +268,8 @@ impl<'r> FromRow<'r, PgRow> for Switch {
firmware_upgrade_status: firmware_upgrade_status.map(|j| j.0),
nvos_update_status: nvos_update_status.map(|j| j.0),
fabric_manager_status: fabric_manager_status.map(|j| j.0),
// A reader may overlap the additive migration during a rolling deployment.
nvlink_domain_uuid: row.try_get("nvlink_domain_uuid").ok().flatten(),
metadata,
version: row.try_get("version")?,
is_primary: row.try_get("is_primary").unwrap_or(false),
Expand Down
Loading
Loading