Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Nexus] Add a sled to an initialized rack #4545

Merged
merged 11 commits into from
Nov 25, 2023
2 changes: 2 additions & 0 deletions nexus/db-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ mod sled;
mod sled_instance;
mod sled_resource;
mod sled_resource_kind;
mod sled_underlay_subnet_allocation;
mod snapshot;
mod ssh_key;
mod switch;
Expand Down Expand Up @@ -153,6 +154,7 @@ pub use sled::*;
pub use sled_instance::*;
pub use sled_resource::*;
pub use sled_resource_kind::*;
pub use sled_underlay_subnet_allocation::*;
pub use snapshot::*;
pub use ssh_key::*;
pub use switch::*;
Expand Down
19 changes: 1 addition & 18 deletions nexus/db-model/src/rack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

use crate::schema::rack;
use db_macros::Asset;
use ipnetwork::{IpNetwork, Ipv6Network};
use ipnetwork::IpNetwork;
use nexus_types::{external_api::views, identity::Asset};
use omicron_common::api;
use uuid::Uuid;

/// Information about a local rack.
Expand All @@ -29,22 +28,6 @@ impl Rack {
rack_subnet: None,
}
}

pub fn subnet(&self) -> Result<Ipv6Network, api::external::Error> {
match self.rack_subnet {
Some(IpNetwork::V6(subnet)) => Ok(subnet),
Some(IpNetwork::V4(_)) => {
return Err(api::external::Error::InternalError {
internal_message: "rack subnet not IPv6".into(),
})
}
None => {
return Err(api::external::Error::InternalError {
internal_message: "rack subnet not set".into(),
})
}
}
}
}

impl From<Rack> for views::Rack {
Expand Down
10 changes: 10 additions & 0 deletions nexus/db-model/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,16 @@ table! {
}
}

table! {
sled_underlay_subnet_allocation (rack_id, sled_id) {
rack_id -> Uuid,
sled_id -> Uuid,
subnet_octet -> Int2,
hw_baseboard_id -> Uuid,
}
}
allow_tables_to_appear_in_same_query!(rack, sled_underlay_subnet_allocation);

table! {
switch (id) {
id -> Uuid,
Expand Down
4 changes: 2 additions & 2 deletions nexus/db-model/src/sled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::ipv6;
use crate::schema::{physical_disk, service, sled, zpool};
use chrono::{DateTime, Utc};
use db_macros::Asset;
use nexus_types::{external_api::views, identity::Asset};
use nexus_types::{external_api::shared, external_api::views, identity::Asset};
use std::net::Ipv6Addr;
use std::net::SocketAddrV6;
use uuid::Uuid;
Expand Down Expand Up @@ -88,7 +88,7 @@ impl From<Sled> for views::Sled {
Self {
identity: sled.identity(),
rack_id: sled.rack_id,
baseboard: views::Baseboard {
baseboard: shared::Baseboard {
serial: sled.serial_number,
part: sled.part_number,
revision: sled.revision,
Expand Down
16 changes: 16 additions & 0 deletions nexus/db-model/src/sled_underlay_subnet_allocation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use crate::schema::sled_underlay_subnet_allocation;
use uuid::Uuid;

/// Underlay allocation for a sled added to an initialized rack
#[derive(Queryable, Insertable, Debug, Clone, Selectable)]
#[diesel(table_name = sled_underlay_subnet_allocation)]
pub struct SledUnderlaySubnetAllocation {
pub rack_id: Uuid,
pub sled_id: Uuid,
pub subnet_octet: i16,
pub hw_baseboard_id: Uuid,
}
4 changes: 2 additions & 2 deletions nexus/db-model/src/switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::Generation;
use crate::schema::switch;
use chrono::{DateTime, Utc};
use db_macros::Asset;
use nexus_types::{external_api::views, identity::Asset};
use nexus_types::{external_api::shared, external_api::views, identity::Asset};
use uuid::Uuid;

/// Baseboard information about a switch.
Expand Down Expand Up @@ -57,7 +57,7 @@ impl From<Switch> for views::Switch {
Self {
identity: switch.identity(),
rack_id: switch.rack_id,
baseboard: views::Baseboard {
baseboard: shared::Baseboard {
serial: switch.serial_number,
part: switch.part_number,
revision: switch.revision,
Expand Down
20 changes: 20 additions & 0 deletions nexus/db-queries/src/db/datastore/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Uuid, Error> {
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::<Uuid>(&*conn)
.await
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
andrewjstone marked this conversation as resolved.
Show resolved Hide resolved
}

/// Attempt to read the latest collection while limiting queries to `limit`
/// records
pub async fn inventory_get_latest_collection(
Expand Down
58 changes: 58 additions & 0 deletions nexus/db-queries/src/db/datastore/rack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use nexus_db_model::InitialDnsGroup;
use nexus_db_model::PasswordHashString;
use nexus_db_model::SiloUser;
use nexus_db_model::SiloUserPasswordHash;
use nexus_db_model::SledUnderlaySubnetAllocation;
use nexus_types::external_api::params as external_params;
use nexus_types::external_api::shared;
use nexus_types::external_api::shared::IdentityType;
Expand Down Expand Up @@ -214,6 +215,63 @@ impl DataStore {
Ok(())
}

// Return the subnet for the rack
pub async fn rack_subnet(
&self,
opctx: &OpContext,
rack_id: Uuid,
) -> Result<IpNetwork, Error> {
opctx.authorize(authz::Action::Read, &authz::FLEET).await?;
let conn = self.pool_connection_authorized(opctx).await?;
use db::schema::rack::dsl;
// It's safe to unwrap the returned `rack_subnet` because
// we filter on `rack_subnet.is_not_null()`
andrewjstone marked this conversation as resolved.
Show resolved Hide resolved
dsl::rack
.filter(dsl::id.eq(rack_id))
.filter(dsl::rack_subnet.is_not_null())
.select(dsl::rack_subnet)
.limit(1)
.first_async::<Option<IpNetwork>>(&*conn)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use .get_result_async here instead of .first_async here, if you want to throw a "Not Found" error.

See: https://docs.rs/async-bb8-diesel/0.1.0/async_bb8_diesel/trait.AsyncRunQueryDsl.html

These are all the ..._async versions of: https://docs.rs/diesel/latest/diesel/query_dsl/trait.RunQueryDsl.html

From get_result specifically:

Err(NotFound) will be returned if the query affected 0 rows.
You can call .optional() on the result of this if the command was optional to get back a Result<Option>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this might be applicable for other spots you're using .first_async ?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great tip! Thank you! I had incorrectly assumed that first_async would also return a "Not Found" error. I will fix and use thisl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, So it looks like first_async does the same thing:

Returns Ok(record) if found, and Err(NotFound) if no results are returned. If the query truly is optional, you can call .optional() on the result of this to get a Result<Option>.

The problem requiring the Option is the nullable field.

.await
.map(|net| net.unwrap())
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
andrewjstone marked this conversation as resolved.
Show resolved Hide resolved
}

/// Return all current underlay allocations for the rack.
///
/// Order allocations by `subnet_octet`
pub async fn rack_subnet_allocations(
&self,
opctx: &OpContext,
rack_id: Uuid,
) -> Result<Vec<SledUnderlaySubnetAllocation>, Error> {
opctx.authorize(authz::Action::Read, &authz::FLEET).await?;
use db::schema::sled_underlay_subnet_allocation::dsl as subnet_dsl;
subnet_dsl::sled_underlay_subnet_allocation
.filter(subnet_dsl::rack_id.eq(rack_id))
.select(SledUnderlaySubnetAllocation::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))
}

/// Store a new sled subnet allocation in the database
pub async fn sled_subnet_allocation_insert(
&self,
opctx: &OpContext,
allocation: &SledUnderlaySubnetAllocation,
) -> Result<(), Error> {
opctx.authorize(authz::Action::Modify, &authz::FLEET).await?;
use db::schema::sled_underlay_subnet_allocation::dsl;
diesel::insert_into(dsl::sled_underlay_subnet_allocation)
.values(allocation.clone())
.execute_async(&*self.pool_connection_authorized(opctx).await?)
.await
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?;
Ok(())
}

// The following methods which return a `TxnError` take a `conn` parameter
// which comes from the transaction created in `rack_set_initialized`.

Expand Down
Loading
Loading