Skip to content

Commit

Permalink
Create a new version of StartSledAgentRequest (#4407)
Browse files Browse the repository at this point in the history
In preparation for sled-addition, a new version of
`StartSledAgentRequest` was created and the old version coverted to V0.
Deterministic conversion from v0 to v1 is possible because we know that
`ntp_servers` and `dns_servers` fields are not in use. We also know the
values of the new fields. `is_lrtq_learner` must be false because we
only support RSS addition on existing sleds.

We then implement a deserialize method that will return the new version
regardless of what's on disk. This is similar to how we handled the
bootstore upgrade for `EarlyNetworkConfig`. Also similar to that, we
extended the format of `StartSledAgentRequest` to ease future
deserializations. In those deserializations we can deserialize the
"header" and use
[serde_json::value::RawValue](https://docs.rs/serde_json/latest/serde_json/value/struct.RawValue.html)
in order to defer deserialization of the `body` field until we know the
`schema_version`.
  • Loading branch information
andrewjstone authored Nov 7, 2023
1 parent bd05e19 commit 3445996
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 119 deletions.
21 changes: 14 additions & 7 deletions common/src/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -84,8 +84,11 @@ impl<T: Ledgerable> Ledger<T> {
// 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)
}
}
}

Expand Down Expand Up @@ -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<Self, Error> {
if path.exists() {
debug!(log, "Reading ledger from {}", path);
serde_json::from_str(
info!(log, "Reading ledger from {}", path);
<Self as Ledgerable>::deserialize(
&tokio::fs::read_to_string(&path)
.await
.map_err(|err| Error::io_path(&path, err))?,
Expand All @@ -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)
}
}
Expand All @@ -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 }
})?;
Expand All @@ -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<Self, serde_json::Error> {
serde_json::from_str(s)
}
}

#[cfg(test)]
Expand Down
45 changes: 29 additions & 16 deletions schema/rss-sled-plan.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
Original file line number Diff line number Diff line change
@@ -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": {
Expand All @@ -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.",
Expand Down
Loading

0 comments on commit 3445996

Please sign in to comment.