diff --git a/nexus/db-queries/src/db/datastore/inventory.rs b/nexus/db-queries/src/db/datastore/inventory.rs index b743d28ee8..0ccdcff9ab 100644 --- a/nexus/db-queries/src/db/datastore/inventory.rs +++ b/nexus/db-queries/src/db/datastore/inventory.rs @@ -39,6 +39,7 @@ use nexus_db_model::InvServiceProcessor; use nexus_db_model::SpType; use nexus_db_model::SpTypeEnum; use nexus_db_model::SwCaboose; +use nexus_types::inventory::BaseboardId; use nexus_types::inventory::Collection; use omicron_common::api::external::Error; use omicron_common::api::external::InternalContext; @@ -798,6 +799,25 @@ impl DataStore { Ok(()) } + // Find the primary key for `hw_baseboard_id` given a `BaseboardId` + pub async fn find_hw_baseboard_id( + &self, + opctx: &OpContext, + baseboard_id: BaseboardId, + ) -> Result { + opctx.authorize(authz::Action::Read, &authz::INVENTORY).await?; + let conn = self.pool_connection_authorized(opctx).await?; + use db::schema::hw_baseboard_id::dsl; + dsl::hw_baseboard_id + .filter(dsl::serial_number.eq(baseboard_id.serial_number)) + .filter(dsl::part_number.eq(baseboard_id.part_number)) + .select(dsl::id) + .limit(1) + .first_async::(&*conn) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + } + /// Attempt to read the latest collection while limiting queries to `limit` /// records pub async fn inventory_get_latest_collection( diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index cd6ebf7c3e..ab927a2815 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -217,6 +217,8 @@ impl DataStore { // Return the rack subnet, reconfiguration epoch, and all current // underlay allocations for the rack. + // + // Order allocations by `subnet_octet` pub async fn rack_subnet_allocations( &self, opctx: &OpContext, @@ -240,6 +242,7 @@ impl DataStore { rack_dsl::reconfiguration_epoch, Option::::as_select(), )) + .order_by(subnet_dsl::subnet_octet.asc()) .load_async(&*self.pool_connection_authorized(opctx).await?) .await .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index 71762dae97..46946173f2 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -13,6 +13,7 @@ use gateway_client::types::SpType; use ipnetwork::IpNetwork; use nexus_db_model::DnsGroup; use nexus_db_model::InitialDnsGroup; +use nexus_db_model::SledUnderlaySubnetAllocation; use nexus_db_model::{SwitchLinkFec, SwitchLinkSpeed}; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; @@ -45,6 +46,7 @@ use omicron_common::api::external::LookupResult; use omicron_common::api::external::Name; use omicron_common::api::external::NameOrId; use omicron_common::api::internal::shared::ExternalPortDiscovery; +use omicron_common::bail_unless; use sled_agent_client::types::EarlyNetworkConfigBody; use sled_agent_client::types::{ BgpConfig, BgpPeerConfig, EarlyNetworkConfig, PortConfigV1, @@ -774,11 +776,52 @@ impl super::Nexus { opctx: &OpContext, sled: UninitializedSled, ) -> Result<(), Error> { - // fetch all the existing allocations via self.rack_id - This only works for a - // a single rack right now - //let allocations = self.db_datastore.rack_subnet_allocations(opctx, rack_id)?; + // First, get the hw_baseboard_id for the given UninitializedSled + let limit = NonZeroU32::new(50).unwrap(); + let collection = self + .db_datastore + .inventory_get_latest_collection(opctx, limit) + .await?; + + let baseboard_id = sled.baseboard.into(); + let hw_baseboard_id = + self.db_datastore.find_hw_baseboard_id(opctx, baseboard_id).await?; + + // Fetch all the existing allocations via self.rack_id + // This only works for a single rack right now + let allocations = self + .db_datastore + .rack_subnet_allocations(opctx, sled.rack_id) + .await?; - // Then calculate the allocation for the new sled + // Calculate the allocation for the new sled by choosing the minimim + // octet. The returned allocations are ordered by octet, so we will know + // when we have a free one. However, if we already have an allocation + // for the given sled then reuse that one. + const MIN_SUBNET_OCTET: i16 = 33; + let mut new_allocation = SledUnderlaySubnetAllocation { + rack_id: sled.rack_id, + sled_id: Uuid::new_v4(), + subnet_octet: MIN_SUBNET_OCTET, + hw_baseboard_id, + }; + for (subnet, epoch, allocation) in allocations { + if let Some(allocation) = allocation { + if allocation.hw_baseboard_id == new_allocation.hw_baseboard_id + { + // We already have an allocation for this sled. + new_allocation = allocation; + break; + } + if allocation.subnet_octet == new_allocation.subnet_octet { + bail_unless!( + new_allocation.subnet_octet < 255, + "Too many sled subnets allocated" + ); + new_allocation.subnet_octet += 1; + } + } + } // Then write the allocation and bump the reconfiguration epoch diff --git a/nexus/types/src/inventory.rs b/nexus/types/src/inventory.rs index 112eec3a65..a838ba634a 100644 --- a/nexus/types/src/inventory.rs +++ b/nexus/types/src/inventory.rs @@ -20,6 +20,8 @@ use std::sync::Arc; use strum::EnumIter; use uuid::Uuid; +use crate::external_api::views::Baseboard; + /// Results of collecting hardware/software inventory from various Omicron /// components /// @@ -108,6 +110,12 @@ pub struct BaseboardId { pub serial_number: String, } +impl From for BaseboardId { + fn from(value: Baseboard) -> Self { + BaseboardId { part_number: value.part, serial_number: value.serial } + } +} + /// Caboose contents found during a collection /// /// These are normalized in the database. Each distinct `Caboose` is assigned a