diff --git a/common/src/ledger.rs b/common/src/ledger.rs index c07fb5e693..ae028998e2 100644 --- a/common/src/ledger.rs +++ b/common/src/ledger.rs @@ -7,7 +7,7 @@ use async_trait::async_trait; use camino::{Utf8Path, Utf8PathBuf}; use serde::{de::DeserializeOwned, Serialize}; -use slog::{debug, warn, Logger}; +use slog::{error, info, warn, Logger}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -84,8 +84,11 @@ impl Ledger { // Read all the ledgers that we can. let mut ledgers = vec![]; for path in paths.iter() { - if let Ok(ledger) = T::read_from(log, &path).await { - ledgers.push(ledger); + match T::read_from(log, &path).await { + Ok(ledger) => ledgers.push(ledger), + Err(err) => { + error!(log, "Failed to read ledger: {err}"; "path" => %path) + } } } @@ -169,8 +172,8 @@ pub trait Ledgerable: DeserializeOwned + Serialize + Send + Sync { /// Reads from `path` as a json-serialized version of `Self`. async fn read_from(log: &Logger, path: &Utf8Path) -> Result { if path.exists() { - debug!(log, "Reading ledger from {}", path); - serde_json::from_str( + info!(log, "Reading ledger from {}", path); + ::deserialize( &tokio::fs::read_to_string(&path) .await .map_err(|err| Error::io_path(&path, err))?, @@ -180,7 +183,7 @@ pub trait Ledgerable: DeserializeOwned + Serialize + Send + Sync { err, }) } else { - debug!(log, "No ledger in {path}"); + warn!(log, "No ledger in {path}"); Err(Error::NotFound) } } @@ -191,7 +194,7 @@ pub trait Ledgerable: DeserializeOwned + Serialize + Send + Sync { log: &Logger, path: &Utf8Path, ) -> Result<(), Error> { - debug!(log, "Writing ledger to {}", path); + info!(log, "Writing ledger to {}", path); let as_str = serde_json::to_string(&self).map_err(|err| { Error::JsonSerialize { path: path.to_path_buf(), err } })?; @@ -200,6 +203,10 @@ pub trait Ledgerable: DeserializeOwned + Serialize + Send + Sync { .map_err(|err| Error::io_path(&path, err))?; Ok(()) } + + fn deserialize(s: &str) -> Result { + serde_json::from_str(s) + } } #[cfg(test)] diff --git a/schema/rss-sled-plan.json b/schema/rss-sled-plan.json index 39a9a68acc..0534c79aef 100644 --- a/schema/rss-sled-plan.json +++ b/schema/rss-sled-plan.json @@ -588,33 +588,46 @@ "description": "Configuration information for launching a Sled Agent.", "type": "object", "required": [ - "dns_servers", + "body", + "generation", + "schema_version" + ], + "properties": { + "body": { + "$ref": "#/definitions/StartSledAgentRequestBody" + }, + "generation": { + "description": "The current generation number of data as stored in CRDB.\n\nThe initial generation is set during RSS time and then only mutated by Nexus. For now, we don't actually anticipate mutating this data, but we leave open the possiblity.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "schema_version": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "StartSledAgentRequestBody": { + "description": "This is the actual app level data of `StartSledAgentRequest`\n\nWe nest it below the \"header\" of `generation` and `schema_version` so that we can perform partial deserialization of `EarlyNetworkConfig` to only read the header and defer deserialization of the body once we know the schema version. This is possible via the use of [`serde_json::value::RawValue`] in future (post-v1) deserialization paths.", + "type": "object", + "required": [ "id", - "ntp_servers", + "is_lrtq_learner", "rack_id", "subnet", "use_trust_quorum" ], "properties": { - "dns_servers": { - "description": "The external DNS servers to use", - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, "id": { "description": "Uuid of the Sled Agent to be created.", "type": "string", "format": "uuid" }, - "ntp_servers": { - "description": "The external NTP servers to use", - "type": "array", - "items": { - "type": "string" - } + "is_lrtq_learner": { + "description": "Is this node an LRTQ learner node?\n\nWe only put the node into learner mode if `use_trust_quorum` is also true.", + "type": "boolean" }, "rack_id": { "description": "Uuid of the rack to which this sled agent belongs.", diff --git a/schema/persistent-sled-agent-request.json b/schema/start-sled-agent-request.json similarity index 54% rename from schema/persistent-sled-agent-request.json rename to schema/start-sled-agent-request.json index 5679165c32..b03058d106 100644 --- a/schema/persistent-sled-agent-request.json +++ b/schema/start-sled-agent-request.json @@ -1,13 +1,27 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PersistentSledAgentRequest", + "title": "StartSledAgentRequest", + "description": "Configuration information for launching a Sled Agent.", "type": "object", "required": [ - "request" + "body", + "generation", + "schema_version" ], "properties": { - "request": { - "$ref": "#/definitions/StartSledAgentRequest" + "body": { + "$ref": "#/definitions/StartSledAgentRequestBody" + }, + "generation": { + "description": "The current generation number of data as stored in CRDB.\n\nThe initial generation is set during RSS time and then only mutated by Nexus. For now, we don't actually anticipate mutating this data, but we leave open the possiblity.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "schema_version": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 } }, "definitions": { @@ -32,37 +46,25 @@ } } }, - "StartSledAgentRequest": { - "description": "Configuration information for launching a Sled Agent.", + "StartSledAgentRequestBody": { + "description": "This is the actual app level data of `StartSledAgentRequest`\n\nWe nest it below the \"header\" of `generation` and `schema_version` so that we can perform partial deserialization of `EarlyNetworkConfig` to only read the header and defer deserialization of the body once we know the schema version. This is possible via the use of [`serde_json::value::RawValue`] in future (post-v1) deserialization paths.", "type": "object", "required": [ - "dns_servers", "id", - "ntp_servers", + "is_lrtq_learner", "rack_id", "subnet", "use_trust_quorum" ], "properties": { - "dns_servers": { - "description": "The external DNS servers to use", - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, "id": { "description": "Uuid of the Sled Agent to be created.", "type": "string", "format": "uuid" }, - "ntp_servers": { - "description": "The external NTP servers to use", - "type": "array", - "items": { - "type": "string" - } + "is_lrtq_learner": { + "description": "Is this node an LRTQ learner node?\n\nWe only put the node into learner mode if `use_trust_quorum` is also true.", + "type": "boolean" }, "rack_id": { "description": "Uuid of the rack to which this sled agent belongs.", diff --git a/sled-agent/src/bootstrap/params.rs b/sled-agent/src/bootstrap/params.rs index 4983383470..cef7bb13bb 100644 --- a/sled-agent/src/bootstrap/params.rs +++ b/sled-agent/src/bootstrap/params.rs @@ -5,8 +5,10 @@ //! Request types for the bootstrap agent use anyhow::{bail, Result}; +use async_trait::async_trait; use omicron_common::address::{self, Ipv6Subnet, SLED_PREFIX}; use omicron_common::api::internal::shared::RackNetworkConfig; +use omicron_common::ledger::Ledgerable; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use sha3::{Digest, Sha3_256}; @@ -172,9 +174,16 @@ impl TryFrom for RackInitializeRequest { pub type Certificate = nexus_client::types::Certificate; pub type RecoverySiloConfig = nexus_client::types::RecoverySiloConfig; -/// Configuration information for launching a Sled Agent. +// A wrapper around StartSledAgentRequestV0 that was used +// for the ledger format. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct StartSledAgentRequest { +struct PersistentSledAgentRequest { + request: StartSledAgentRequestV0, +} + +/// The version of `StartSledAgentRequest` we originally shipped with. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct StartSledAgentRequestV0 { /// Uuid of the Sled Agent to be created. pub id: Uuid, @@ -197,13 +206,62 @@ pub struct StartSledAgentRequest { pub subnet: Ipv6Subnet, } +/// Configuration information for launching a Sled Agent. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct StartSledAgentRequest { + /// The current generation number of data as stored in CRDB. + /// + /// The initial generation is set during RSS time and then only mutated + /// by Nexus. For now, we don't actually anticipate mutating this data, + /// but we leave open the possiblity. + pub generation: u64, + + // Which version of the data structure do we have. This is to help with + // deserialization and conversion in future updates. + pub schema_version: u32, + + // The actual configuration details + pub body: StartSledAgentRequestBody, +} + +/// This is the actual app level data of `StartSledAgentRequest` +/// +/// We nest it below the "header" of `generation` and `schema_version` so that +/// we can perform partial deserialization of `EarlyNetworkConfig` to only read +/// the header and defer deserialization of the body once we know the schema +/// version. This is possible via the use of [`serde_json::value::RawValue`] in +/// future (post-v1) deserialization paths. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct StartSledAgentRequestBody { + /// Uuid of the Sled Agent to be created. + pub id: Uuid, + + /// Uuid of the rack to which this sled agent belongs. + pub rack_id: Uuid, + + /// Use trust quorum for key generation + pub use_trust_quorum: bool, + + /// Is this node an LRTQ learner node? + /// + /// We only put the node into learner mode if `use_trust_quorum` is also + /// true. + pub is_lrtq_learner: bool, + + // Note: The order of these fields is load bearing, because we serialize + // `SledAgentRequest`s as toml. `subnet` serializes as a TOML table, so it + // must come after non-table fields. + /// Portion of the IP space to be managed by the Sled Agent. + pub subnet: Ipv6Subnet, +} + impl StartSledAgentRequest { pub fn sled_address(&self) -> SocketAddrV6 { - address::get_sled_address(self.subnet) + address::get_sled_address(self.body.subnet) } pub fn switch_zone_ip(&self) -> Ipv6Addr { - address::get_switch_zone_address(self.subnet) + address::get_switch_zone_address(self.body.subnet) } /// Compute the sha3_256 digest of `self.rack_id` to use as a `salt` @@ -212,7 +270,57 @@ impl StartSledAgentRequest { /// between sleds. pub fn hash_rack_id(&self) -> [u8; 32] { // We know the unwrap succeeds as a Sha3_256 digest is 32 bytes - Sha3_256::digest(self.rack_id.as_bytes()).as_slice().try_into().unwrap() + Sha3_256::digest(self.body.rack_id.as_bytes()) + .as_slice() + .try_into() + .unwrap() + } +} + +impl From for StartSledAgentRequest { + fn from(v0: StartSledAgentRequestV0) -> Self { + StartSledAgentRequest { + generation: 0, + schema_version: 1, + body: StartSledAgentRequestBody { + id: v0.id, + rack_id: v0.rack_id, + use_trust_quorum: v0.use_trust_quorum, + is_lrtq_learner: false, + subnet: v0.subnet, + }, + } + } +} + +#[async_trait] +impl Ledgerable for StartSledAgentRequest { + fn is_newer_than(&self, other: &Self) -> bool { + self.generation > other.generation + } + + fn generation_bump(&mut self) { + // DO NOTHING! + // + // Generation bumps must only ever come from nexus and will be encoded + // in the struct itself + } + + // Attempt to deserialize the v1 or v0 version and return + // the v1 version. + fn deserialize( + s: &str, + ) -> Result { + // Try to deserialize the latest version of the data structure (v1). If + // that succeeds we are done. + if let Ok(val) = serde_json::from_str::(s) { + return Ok(val); + } + + // We don't have the latest version. Try to deserialize v0 and then + // convert it to the latest version. + let v0 = serde_json::from_str::(s)?.request; + Ok(v0.into()) } } @@ -291,12 +399,15 @@ mod tests { version: 1, request: Request::StartSledAgentRequest(Cow::Owned( StartSledAgentRequest { - id: Uuid::new_v4(), - rack_id: Uuid::new_v4(), - ntp_servers: vec![String::from("test.pool.example.com")], - dns_servers: vec!["1.1.1.1".parse().unwrap()], - use_trust_quorum: false, - subnet: Ipv6Subnet::new(Ipv6Addr::LOCALHOST), + generation: 0, + schema_version: 1, + body: StartSledAgentRequestBody { + id: Uuid::new_v4(), + rack_id: Uuid::new_v4(), + use_trust_quorum: false, + is_lrtq_learner: false, + subnet: Ipv6Subnet::new(Ipv6Addr::LOCALHOST), + }, }, )), }; @@ -308,6 +419,36 @@ mod tests { assert!(envelope == deserialized, "serialization round trip failed"); } + #[test] + fn serialize_start_sled_agent_v0_deserialize_v1() { + let v0 = PersistentSledAgentRequest { + request: StartSledAgentRequestV0 { + id: Uuid::new_v4(), + rack_id: Uuid::new_v4(), + ntp_servers: vec![String::from("test.pool.example.com")], + dns_servers: vec!["1.1.1.1".parse().unwrap()], + use_trust_quorum: false, + subnet: Ipv6Subnet::new(Ipv6Addr::LOCALHOST), + }, + }; + let serialized = serde_json::to_string(&v0).unwrap(); + let expected = StartSledAgentRequest { + generation: 0, + schema_version: 1, + body: StartSledAgentRequestBody { + id: v0.request.id, + rack_id: v0.request.rack_id, + use_trust_quorum: v0.request.use_trust_quorum, + is_lrtq_learner: false, + subnet: v0.request.subnet, + }, + }; + + let actual: StartSledAgentRequest = + Ledgerable::deserialize(&serialized).unwrap(); + assert_eq!(expected, actual); + } + #[test] fn validate_external_dns_ips_must_be_in_internal_services_ip_pools() { // Conjure up a config; we'll tweak the internal services pools and diff --git a/sled-agent/src/bootstrap/server.rs b/sled-agent/src/bootstrap/server.rs index 9ed3ad582d..242cfdd1c7 100644 --- a/sled-agent/src/bootstrap/server.rs +++ b/sled-agent/src/bootstrap/server.rs @@ -41,14 +41,9 @@ use illumos_utils::zone; use illumos_utils::zone::Zones; use omicron_common::ledger; use omicron_common::ledger::Ledger; -use omicron_common::ledger::Ledgerable; -use schemars::JsonSchema; -use serde::Deserialize; -use serde::Serialize; use sled_hardware::underlay; use sled_hardware::HardwareUpdate; use slog::Logger; -use std::borrow::Cow; use std::io; use std::net::SocketAddr; use std::net::SocketAddrV6; @@ -225,11 +220,8 @@ impl Server { async { let paths = sled_config_paths(storage_resources).await?; let maybe_ledger = - Ledger::>::new( - &startup_log, - paths, - ) - .await; + Ledger::::new(&startup_log, paths) + .await; Ok::<_, StartError>(maybe_ledger) }, &mut hardware_monitor, @@ -288,11 +280,11 @@ impl Server { // Do we have a persistent sled-agent request that we need to restore? let state = if let Some(ledger) = maybe_ledger { - let sled_request = ledger.data(); + let start_sled_agent_request = ledger.into_inner(); let sled_agent_server = wait_while_handling_hardware_updates( start_sled_agent( &config, - &sled_request.request, + start_sled_agent_request, &bootstore_handles.node_handle, &managers, &ddm_admin_localhost_client, @@ -426,7 +418,7 @@ impl From for StartError { async fn start_sled_agent( config: &SledConfig, - request: &StartSledAgentRequest, + request: StartSledAgentRequest, bootstore: &bootstore::NodeHandle, managers: &BootstrapManagers, ddmd_client: &DdmAdminClient, @@ -440,7 +432,7 @@ async fn start_sled_agent( // storage manager about keys, advertising prefixes, ...). // Initialize the secret retriever used by the `KeyManager` - if request.use_trust_quorum { + if request.body.use_trust_quorum { info!(log, "KeyManager: using lrtq secret retriever"); let salt = request.hash_rack_id(); LrtqOrHardcodedSecretRetriever::init_lrtq(salt, bootstore.clone()) @@ -463,7 +455,7 @@ async fn start_sled_agent( // we'll need to do something different here for underlay vs bootstrap // addrs (either talk to a differently-configured ddmd, or include info // indicating which kind of address we're advertising). - ddmd_client.advertise_prefix(request.subnet); + ddmd_client.advertise_prefix(request.body.subnet); // Server does not exist, initialize it. let server = SledAgentServer::start( @@ -483,11 +475,7 @@ async fn start_sled_agent( // initialized on the next boot. let paths = sled_config_paths(managers.storage.resources()).await?; - let mut ledger = Ledger::new_with( - &log, - paths, - PersistentSledAgentRequest { request: Cow::Borrowed(request) }, - ); + let mut ledger = Ledger::new_with(&log, paths, request); ledger.commit().await?; Ok(server) @@ -611,18 +599,6 @@ async fn wait_while_handling_hardware_updates, T>( } } -#[derive(Clone, Serialize, Deserialize, PartialEq, JsonSchema)] -struct PersistentSledAgentRequest<'a> { - request: Cow<'a, StartSledAgentRequest>, -} - -impl<'a> Ledgerable for PersistentSledAgentRequest<'a> { - fn is_newer_than(&self, _other: &Self) -> bool { - true - } - fn generation_bump(&mut self) {} -} - /// Runs the OpenAPI generator, emitting the spec to stdout. pub fn run_openapi() -> Result<(), String> { http_api() @@ -717,11 +693,12 @@ impl Inner { response_tx: oneshot::Sender>, log: &Logger, ) { + let request_id = request.body.id; match &self.state { SledAgentState::Bootstrapping => { let response = match start_sled_agent( &self.config, - &request, + request, &self.bootstore_handles.node_handle, &self.managers, &self.ddm_admin_localhost_client, @@ -742,7 +719,7 @@ impl Inner { .await; self.state = SledAgentState::ServerStarted(server); - Ok(SledAgentResponse { id: request.id }) + Ok(SledAgentResponse { id: request_id }) } Err(err) => Err(format!("{err:#}")), }; @@ -752,12 +729,12 @@ impl Inner { info!(log, "Sled Agent already loaded"); let sled_address = request.sled_address(); - let response = if server.id() != request.id { + let response = if server.id() != request_id { Err(format!( "Sled Agent already running with UUID {}, \ but {} was requested", server.id(), - request.id, + request_id, )) } else if &server.address().ip() != sled_address.ip() { Err(format!( @@ -873,6 +850,8 @@ impl Inner { #[cfg(test)] mod tests { + use crate::bootstrap::params::StartSledAgentRequestBody; + use super::*; use omicron_common::address::Ipv6Subnet; use omicron_test_utils::dev::test_setup_log; @@ -880,20 +859,21 @@ mod tests { use uuid::Uuid; #[tokio::test] - async fn persistent_sled_agent_request_serialization() { + async fn start_sled_agent_request_serialization() { let logctx = test_setup_log("persistent_sled_agent_request_serialization"); let log = &logctx.log; - let request = PersistentSledAgentRequest { - request: Cow::Owned(StartSledAgentRequest { + let request = StartSledAgentRequest { + generation: 0, + schema_version: 1, + body: StartSledAgentRequestBody { id: Uuid::new_v4(), rack_id: Uuid::new_v4(), - ntp_servers: vec![String::from("test.pool.example.com")], - dns_servers: vec!["1.1.1.1".parse().unwrap()], use_trust_quorum: false, + is_lrtq_learner: false, subnet: Ipv6Subnet::new(Ipv6Addr::LOCALHOST), - }), + }, }; let tempdir = camino_tempfile::Utf8TempDir::new().unwrap(); @@ -902,7 +882,7 @@ mod tests { let mut ledger = Ledger::new_with(log, paths.clone(), request.clone()); ledger.commit().await.expect("Failed to write to ledger"); - let ledger = Ledger::::new(log, paths) + let ledger = Ledger::::new(log, paths) .await .expect("Failed to read request"); @@ -912,9 +892,9 @@ mod tests { #[test] fn test_persistent_sled_agent_request_schema() { - let schema = schemars::schema_for!(PersistentSledAgentRequest<'_>); + let schema = schemars::schema_for!(StartSledAgentRequest); expectorate::assert_contents( - "../schema/persistent-sled-agent-request.json", + "../schema/start-sled-agent-request.json", &serde_json::to_string_pretty(&schema).unwrap(), ); } diff --git a/sled-agent/src/rack_setup/plan/service.rs b/sled-agent/src/rack_setup/plan/service.rs index 3dac5d7d1e..8cd815e7fb 100644 --- a/sled-agent/src/rack_setup/plan/service.rs +++ b/sled-agent/src/rack_setup/plan/service.rs @@ -249,7 +249,7 @@ impl Plan { let result: Result, PlanError> = futures::future::try_join_all(sleds.values().map( |sled_request| async { - let subnet = sled_request.subnet; + let subnet = sled_request.body.subnet; let sled_address = get_sled_address(subnet); let u2_zpools = Self::get_u2_zpools_from_sled(log, sled_address) @@ -257,7 +257,7 @@ impl Plan { let is_scrimlet = Self::is_sled_scrimlet(log, sled_address).await?; Ok(SledInfo::new( - sled_request.id, + sled_request.body.id, subnet, sled_address, u2_zpools, diff --git a/sled-agent/src/rack_setup/plan/sled.rs b/sled-agent/src/rack_setup/plan/sled.rs index ea12f0db32..163b24cd45 100644 --- a/sled-agent/src/rack_setup/plan/sled.rs +++ b/sled-agent/src/rack_setup/plan/sled.rs @@ -4,6 +4,7 @@ //! Plan generation for "how should sleds be initialized". +use crate::bootstrap::params::StartSledAgentRequestBody; use crate::bootstrap::{ config::BOOTSTRAP_AGENT_RACK_INIT_PORT, params::StartSledAgentRequest, }; @@ -99,12 +100,15 @@ impl Plan { ( bootstrap_addr, StartSledAgentRequest { - id: Uuid::new_v4(), - subnet, - ntp_servers: config.ntp_servers.clone(), - dns_servers: config.dns_servers.clone(), - use_trust_quorum, - rack_id, + generation: 0, + schema_version: 1, + body: StartSledAgentRequestBody { + id: Uuid::new_v4(), + subnet, + use_trust_quorum, + is_lrtq_learner: false, + rack_id, + }, }, ) }); diff --git a/sled-agent/src/rack_setup/service.rs b/sled-agent/src/rack_setup/service.rs index 7f6469d2c0..5657c7e69a 100644 --- a/sled-agent/src/rack_setup/service.rs +++ b/sled-agent/src/rack_setup/service.rs @@ -535,8 +535,10 @@ impl ServiceInner { // We need the ID when passing info to Nexus. let mut id_map = HashMap::new(); for (_, sled_request) in sled_plan.sleds.iter() { - id_map - .insert(get_sled_address(sled_request.subnet), sled_request.id); + id_map.insert( + get_sled_address(sled_request.body.subnet), + sled_request.body.id, + ); } // Convert all the information we have about services and datasets into @@ -925,7 +927,7 @@ impl ServiceInner { .sleds .values() .map(|initialization_request| { - get_sled_address(initialization_request.subnet) + get_sled_address(initialization_request.body.subnet) }) .collect(); let service_plan = if let Some(plan) = diff --git a/sled-agent/src/sled_agent.rs b/sled-agent/src/sled_agent.rs index 6655a732a0..b29c7e1af4 100644 --- a/sled-agent/src/sled_agent.rs +++ b/sled-agent/src/sled_agent.rs @@ -278,7 +278,7 @@ impl SledAgent { // Use "log" for ourself. let log = log.new(o!( "component" => "SledAgent", - "sled_id" => request.id.to_string(), + "sled_id" => request.body.id.to_string(), )); info!(&log, "SledAgent::new(..) starting"); @@ -347,7 +347,7 @@ impl SledAgent { storage .setup_underlay_access(storage_manager::UnderlayAccess { nexus_client: nexus_client.clone(), - sled_id: request.id, + sled_id: request.body.id, }) .await?; @@ -391,8 +391,10 @@ impl SledAgent { }; let updates = UpdateManager::new(update_config); - let svc_config = - services::Config::new(request.id, config.sidecar_revision.clone()); + let svc_config = services::Config::new( + request.body.id, + config.sidecar_revision.clone(), + ); // Get our rack network config from the bootstore; we cannot proceed // until we have this, as we need to know which switches have uplinks to @@ -437,15 +439,15 @@ impl SledAgent { svc_config, port_manager.clone(), *sled_address.ip(), - request.rack_id, + request.body.rack_id, rack_network_config.clone(), )?; let zone_bundler = storage.zone_bundler().clone(); let sled_agent = SledAgent { inner: Arc::new(SledAgentInner { - id: request.id, - subnet: request.subnet, + id: request.body.id, + subnet: request.body.subnet, storage, instances, hardware,