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
14 changes: 13 additions & 1 deletion Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ members = [
"test-utils",
"tufaceous-lib",
"tufaceous",
"typed-rng",
"update-common",
"update-engine",
"uuid-kinds",
Expand Down Expand Up @@ -149,6 +150,7 @@ default-members = [
"test-utils",
"tufaceous-lib",
"tufaceous",
"typed-rng",
"update-common",
"update-engine",
"uuid-kinds",
Expand Down Expand Up @@ -343,6 +345,7 @@ propolis-mock-server = { git = "https://github.com/oxidecomputer/propolis", rev
proptest = "1.4.0"
quote = "1.0"
rand = "0.8.5"
rand_core = "0.6.4"
rand_seeder = "0.2.3"
ratatui = "0.26.1"
rayon = "1.9"
Expand Down Expand Up @@ -434,6 +437,7 @@ trybuild = "1.0.89"
tufaceous = { path = "tufaceous" }
tufaceous-lib = { path = "tufaceous-lib" }
tui-tree-widget = "0.17.0"
typed-rng = { path = "typed-rng" }
unicode-width = "0.1.11"
update-common = { path = "update-common" }
update-engine = { path = "update-engine" }
Expand Down
1 change: 1 addition & 0 deletions nexus/inventory/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ sled-agent-client.workspace = true
slog.workspace = true
strum.workspace = true
thiserror.workspace = true
typed-rng.workspace = true
uuid.workspace = true
omicron-workspace-hack.workspace = true

Expand Down
19 changes: 18 additions & 1 deletion nexus/inventory/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ use nexus_types::inventory::SledAgent;
use nexus_types::inventory::Zpool;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::hash::Hash;
use std::sync::Arc;
use thiserror::Error;
use typed_rng::UuidRng;
use uuid::Uuid;

/// Describes an operational error encountered during the collection process
Expand Down Expand Up @@ -86,6 +88,8 @@ pub struct CollectionBuilder {
BTreeMap<RotPageWhich, BTreeMap<Arc<BaseboardId>, RotPageFound>>,
sleds: BTreeMap<Uuid, SledAgent>,
omicron_zones: BTreeMap<Uuid, OmicronZonesFound>,
// We just generate one UUID for each collection.
id_rng: UuidRng,
}

impl CollectionBuilder {
Expand All @@ -111,6 +115,7 @@ impl CollectionBuilder {
rot_pages_found: BTreeMap::new(),
sleds: BTreeMap::new(),
omicron_zones: BTreeMap::new(),
id_rng: UuidRng::from_entropy(),
}
}

Expand All @@ -123,7 +128,7 @@ impl CollectionBuilder {
}

Collection {
id: Uuid::new_v4(),
id: self.id_rng.next(),
errors: self.errors.into_iter().map(|e| e.to_string()).collect(),
time_started: self.time_started,
time_done: now_db_precision(),
Expand All @@ -140,6 +145,18 @@ impl CollectionBuilder {
}
}

/// Within tests, set a seeded RNG for deterministic results.
///
/// This will ensure that tests that use this builder will produce the same
/// results each time they are run.
pub fn set_rng_seed<H: Hash>(&mut self, seed: H) -> &mut Self {
// Important to add some more bytes here, so that builders with the
// same seed but different purposes don't end up with the same UUIDs.
const SEED_EXTRA: &str = "collection-builder";
self.id_rng.set_seed(seed, SEED_EXTRA);
self
}

/// Record service processor state `sp_state` reported by MGS
///
/// `sp_type` and `slot` identify which SP this was.
Expand Down
2 changes: 1 addition & 1 deletion nexus/reconfigurator/planning/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ nexus-inventory.workspace = true
nexus-types.workspace = true
omicron-common.workspace = true
rand.workspace = true
rand_seeder.workspace = true
sled-agent-client.workspace = true
slog.workspace = true
thiserror.workspace = true
typed-rng.workspace = true
uuid.workspace = true

omicron-workspace-hack.workspace = true
Expand Down
61 changes: 14 additions & 47 deletions nexus/reconfigurator/planning/src/blueprint_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ use omicron_common::api::external::Vni;
use omicron_common::api::internal::shared::NetworkInterface;
use omicron_common::api::internal::shared::NetworkInterfaceKind;
use rand::rngs::StdRng;
use rand::RngCore;
use rand::SeedableRng;
use slog::o;
use slog::Logger;
Expand All @@ -50,6 +49,7 @@ use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddrV6;
use thiserror::Error;
use typed_rng::UuidRng;
use uuid::Uuid;

/// Errors encountered while assembling blueprints
Expand Down Expand Up @@ -223,7 +223,7 @@ impl<'a> BlueprintBuilder<'a> {
})
.collect::<Result<_, Error>>()?;
Ok(Blueprint {
id: rng.blueprint_rng.next_uuid(),
id: rng.blueprint_rng.next(),
blueprint_zones,
parent_blueprint_id: None,
internal_dns_version,
Expand Down Expand Up @@ -375,7 +375,7 @@ impl<'a> BlueprintBuilder<'a> {
let blueprint_zones =
self.zones.into_zones_map(self.policy.sleds.keys().copied());
Blueprint {
id: self.rng.blueprint_rng.next_uuid(),
id: self.rng.blueprint_rng.next(),
blueprint_zones,
parent_blueprint_id: Some(self.parent_blueprint.id),
internal_dns_version: self.internal_dns_version,
Expand Down Expand Up @@ -452,7 +452,7 @@ impl<'a> BlueprintBuilder<'a> {
.collect();

let zone = OmicronZoneConfig {
id: self.rng.zone_rng.next_uuid(),
id: self.rng.zone_rng.next(),
underlay_address: ip,
zone_type: OmicronZoneType::InternalNtp {
address: ntp_address.to_string(),
Expand Down Expand Up @@ -502,7 +502,7 @@ impl<'a> BlueprintBuilder<'a> {
let port = omicron_common::address::CRUCIBLE_PORT;
let address = SocketAddrV6::new(ip, port, 0, 0).to_string();
let zone = OmicronZoneConfig {
id: self.rng.zone_rng.next_uuid(),
id: self.rng.zone_rng.next(),
underlay_address: ip,
zone_type: OmicronZoneType::Crucible {
address,
Expand Down Expand Up @@ -589,7 +589,7 @@ impl<'a> BlueprintBuilder<'a> {
};

for _ in 0..num_nexus_to_add {
let nexus_id = self.rng.zone_rng.next_uuid();
let nexus_id = self.rng.zone_rng.next();
let external_ip = self
.available_external_ips
.next()
Expand Down Expand Up @@ -617,7 +617,7 @@ impl<'a> BlueprintBuilder<'a> {
.next()
.ok_or(Error::NoSystemMacAddressAvailable)?;
NetworkInterface {
id: self.rng.network_interface_rng.next_uuid(),
id: self.rng.network_interface_rng.next(),
kind: NetworkInterfaceKind::Service { id: nexus_id },
name: format!("nexus-{nexus_id}").parse().unwrap(),
ip,
Expand Down Expand Up @@ -739,14 +739,14 @@ struct BlueprintBuilderRng {

impl BlueprintBuilderRng {
fn new() -> Self {
Self::new_from_rng(StdRng::from_entropy())
Self::new_from_parent(StdRng::from_entropy())
}

fn new_from_rng(mut root_rng: StdRng) -> Self {
let blueprint_rng = UuidRng::from_root_rng(&mut root_rng, "blueprint");
let zone_rng = UuidRng::from_root_rng(&mut root_rng, "zone");
fn new_from_parent(mut parent: StdRng) -> Self {
let blueprint_rng = UuidRng::from_parent_rng(&mut parent, "blueprint");
let zone_rng = UuidRng::from_parent_rng(&mut parent, "zone");
let network_interface_rng =
UuidRng::from_root_rng(&mut root_rng, "network_interface");
UuidRng::from_parent_rng(&mut parent, "network_interface");

BlueprintBuilderRng { blueprint_rng, zone_rng, network_interface_rng }
}
Expand All @@ -755,40 +755,7 @@ impl BlueprintBuilderRng {
// Important to add some more bytes here, so that builders with the
// same seed but different purposes don't end up with the same UUIDs.
const SEED_EXTRA: &str = "blueprint-builder";
let mut seeder = rand_seeder::Seeder::from((seed, SEED_EXTRA));
*self = Self::new_from_rng(seeder.make_rng::<StdRng>());
}
}

#[derive(Debug)]
pub(crate) struct UuidRng {
rng: StdRng,
}

impl UuidRng {
/// Returns a new `UuidRng` generated from the root RNG.
///
/// `extra` is a string that should be unique to the purpose of the UUIDs.
fn from_root_rng(root_rng: &mut StdRng, extra: &'static str) -> Self {
let seed = root_rng.next_u64();
let mut seeder = rand_seeder::Seeder::from((seed, extra));
Self { rng: seeder.make_rng::<StdRng>() }
}

/// `extra` is a string that should be unique to the purpose of the UUIDs.
pub(crate) fn from_seed<H: Hash>(seed: H, extra: &'static str) -> Self {
let mut seeder = rand_seeder::Seeder::from((seed, extra));
Self { rng: seeder.make_rng::<StdRng>() }
}

/// Returns a new UUIDv4 generated from the RNG.
pub(crate) fn next_uuid(&mut self) -> Uuid {
let mut bytes = [0; 16];
self.rng.fill_bytes(&mut bytes);
// Builder::from_random_bytes will turn the random bytes into a valid
// UUIDv4. (Parts of the system depend on the UUID actually being valid
// v4, so it's important that we don't just use `uuid::from_bytes`.)
uuid::Builder::from_random_bytes(bytes).into_uuid()
*self = Self::new_from_parent(typed_rng::from_seed(seed, SEED_EXTRA));
}
}

Expand Down Expand Up @@ -1013,7 +980,7 @@ pub mod test {
assert_eq!(diff.sleds_changed().count(), 0);

// The next step is adding these zones to a new sled.
let new_sled_id = example.sled_rng.next_uuid();
let new_sled_id = example.sled_rng.next();
let _ =
example.system.sled(SledBuilder::new().id(new_sled_id)).unwrap();
let policy = example.system.to_policy().unwrap();
Expand Down
6 changes: 3 additions & 3 deletions nexus/reconfigurator/planning/src/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
//! Example blueprints

use crate::blueprint_builder::BlueprintBuilder;
use crate::blueprint_builder::UuidRng;
use crate::system::SledBuilder;
use crate::system::SystemDescription;
use nexus_types::deployment::Blueprint;
Expand All @@ -14,6 +13,7 @@ use nexus_types::deployment::Policy;
use nexus_types::inventory::Collection;
use omicron_common::api::external::Generation;
use sled_agent_client::types::OmicronZonesConfig;
use typed_rng::UuidRng;

pub struct ExampleSystem {
pub system: SystemDescription,
Expand All @@ -38,8 +38,7 @@ impl ExampleSystem {
) -> ExampleSystem {
let mut system = SystemDescription::new();
let mut sled_rng = UuidRng::from_seed(test_name, "ExampleSystem");
let sled_ids: Vec<_> =
(0..nsleds).map(|_| sled_rng.next_uuid()).collect();
let sled_ids: Vec<_> = (0..nsleds).map(|_| sled_rng.next()).collect();
for sled_id in &sled_ids {
let _ = system.sled(SledBuilder::new().id(*sled_id)).unwrap();
}
Expand Down Expand Up @@ -107,6 +106,7 @@ impl ExampleSystem {
let blueprint = builder.build();
let mut builder =
system.to_collection_builder().expect("failed to build collection");
builder.set_rng_seed((test_name, "ExampleSystem collection"));

for sled_id in blueprint.sleds() {
let Some(zones) = blueprint.blueprint_zones.get(&sled_id) else {
Expand Down
2 changes: 1 addition & 1 deletion nexus/reconfigurator/planning/src/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ mod test {
verify_blueprint(&blueprint2);

// Now add a new sled.
let new_sled_id = example.sled_rng.next_uuid();
let new_sled_id = example.sled_rng.next();
let _ =
example.system.sled(SledBuilder::new().id(new_sled_id)).unwrap();
let policy = example.system.to_policy().unwrap();
Expand Down
11 changes: 11 additions & 0 deletions typed-rng/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "typed-rng"
version = "0.1.0"
edition = "2021"

[dependencies]
omicron-workspace-hack.workspace = true
rand.workspace = true
rand_core.workspace = true
rand_seeder.workspace = true
uuid.workspace = true
Loading