Skip to content

Commit 79e918f

Browse files
plaidfinchclaude
andcommitted
Adds version 13 (ADD_TRUST_QUORUM) to the Sled Agent API with the following endpoints for trust quorum reconfiguration:
- POST `/trust-quorum/reconfigure` - Initiate a reconfiguration - POST `/trust-quorum/upgrade-from-lrtq` - Upgrade from low-rent (legacy) trust quorum - POST `/trust-quorum/commit` - Commit a trust-quorum - GET `/trust-quorum/coordinator-status` - Get coordinator status - POST `/trust-quorum/prepare-and-commit` - Prepare and commit a configuration Types are organized per RFD 619 (via feeding Claude the RFD): - API types defined in `sled-agent-types-versions/src/add_trust_quorum/` - Re-exported via `latest.rs` and `sled-agent-types/src/trust_quorum.rs` - API trait uses `latest::` paths for all trust quorum types Also exports `EncryptedRackSecrets`, `Salt`, and `Sha3_256Digest` from `trust-quorum-protocol` for use in the `prepare_and_commit` handler. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 63c0189 commit 79e918f

File tree

13 files changed

+10131
-3
lines changed

13 files changed

+10131
-3
lines changed

openapi/sled-agent/sled-agent-13.0.0-015e5e.json

Lines changed: 9693 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sled-agent-12.0.0-ffacab.json
1+
sled-agent-13.0.0-015e5e.json

sled-agent/api/src/lib.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ api_versions!([
3434
// | example for the next person.
3535
// v
3636
// (next_int, IDENT),
37+
(13, ADD_TRUST_QUORUM),
3738
(12, ADD_SMF_SERVICES_HEALTH_CHECK),
3839
(11, ADD_DUAL_STACK_EXTERNAL_IP_CONFIG),
3940
(10, ADD_DUAL_STACK_SHARED_NETWORK_INTERFACES),
@@ -1065,4 +1066,71 @@ pub trait SledAgentApi {
10651066
request_context: RequestContext<Self::Context>,
10661067
path_params: Path<latest::dataset::LocalStoragePathParam>,
10671068
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
1069+
1070+
/// Initiate a trust quorum reconfiguration
1071+
#[endpoint {
1072+
method = POST,
1073+
path = "/trust-quorum/reconfigure",
1074+
versions = VERSION_ADD_TRUST_QUORUM..,
1075+
}]
1076+
async fn trust_quorum_reconfigure(
1077+
request_context: RequestContext<Self::Context>,
1078+
body: TypedBody<latest::trust_quorum::TrustQuorumReconfigureRequest>,
1079+
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
1080+
1081+
/// Initiate an upgrade from LRTQ
1082+
#[endpoint {
1083+
method = POST,
1084+
path = "/trust-quorum/upgrade-from-lrtq",
1085+
versions = VERSION_ADD_TRUST_QUORUM..,
1086+
}]
1087+
async fn trust_quorum_upgrade_from_lrtq(
1088+
request_context: RequestContext<Self::Context>,
1089+
body: TypedBody<latest::trust_quorum::TrustQuorumLrtqUpgradeRequest>,
1090+
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
1091+
1092+
/// Commit a trust quorum configuration
1093+
#[endpoint {
1094+
method = POST,
1095+
path = "/trust-quorum/commit",
1096+
versions = VERSION_ADD_TRUST_QUORUM..,
1097+
}]
1098+
async fn trust_quorum_commit(
1099+
request_context: RequestContext<Self::Context>,
1100+
body: TypedBody<latest::trust_quorum::TrustQuorumCommitRequest>,
1101+
) -> Result<
1102+
HttpResponseOk<latest::trust_quorum::TrustQuorumCommitResponse>,
1103+
HttpError,
1104+
>;
1105+
1106+
/// Get the coordinator status if this node is coordinating a reconfiguration
1107+
#[endpoint {
1108+
method = GET,
1109+
path = "/trust-quorum/coordinator-status",
1110+
versions = VERSION_ADD_TRUST_QUORUM..,
1111+
}]
1112+
async fn trust_quorum_coordinator_status(
1113+
request_context: RequestContext<Self::Context>,
1114+
) -> Result<
1115+
HttpResponseOk<
1116+
Option<latest::trust_quorum::TrustQuorumCoordinatorStatus>,
1117+
>,
1118+
HttpError,
1119+
>;
1120+
1121+
/// Attempt to prepare and commit a trust quorum configuration
1122+
#[endpoint {
1123+
method = POST,
1124+
path = "/trust-quorum/prepare-and-commit",
1125+
versions = VERSION_ADD_TRUST_QUORUM..,
1126+
}]
1127+
async fn trust_quorum_prepare_and_commit(
1128+
request_context: RequestContext<Self::Context>,
1129+
body: TypedBody<
1130+
latest::trust_quorum::TrustQuorumPrepareAndCommitRequest,
1131+
>,
1132+
) -> Result<
1133+
HttpResponseOk<latest::trust_quorum::TrustQuorumCommitResponse>,
1134+
HttpError,
1135+
>;
10681136
}

sled-agent/src/http_entrypoints.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ use sled_agent_types::support_bundle::{
5555
SupportBundleMetadata, SupportBundlePathParam,
5656
SupportBundleTransferQueryParams,
5757
};
58+
use sled_agent_types::trust_quorum::{
59+
TrustQuorumCommitRequest, TrustQuorumCommitResponse,
60+
TrustQuorumConfiguration, TrustQuorumCoordinatorStatus,
61+
TrustQuorumLrtqUpgradeRequest, TrustQuorumPrepareAndCommitRequest,
62+
TrustQuorumReconfigureRequest,
63+
};
5864
use sled_agent_types::zone_bundle::{
5965
BundleUtilization, CleanupContext, CleanupContextUpdate, CleanupCount,
6066
CleanupPeriod, StorageLimit, ZoneBundleFilter, ZoneBundleId,
@@ -1178,4 +1184,184 @@ impl SledAgentApi for SledAgentImpl {
11781184

11791185
Ok(HttpResponseUpdatedNoContent())
11801186
}
1187+
1188+
async fn trust_quorum_reconfigure(
1189+
request_context: RequestContext<Self::Context>,
1190+
body: TypedBody<TrustQuorumReconfigureRequest>,
1191+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
1192+
let sa = request_context.context();
1193+
let request = body.into_inner();
1194+
1195+
let msg = trust_quorum_protocol::ReconfigureMsg {
1196+
rack_id: request.rack_id,
1197+
epoch: trust_quorum_protocol::Epoch(request.epoch),
1198+
last_committed_epoch: request
1199+
.last_committed_epoch
1200+
.map(trust_quorum_protocol::Epoch),
1201+
members: request.members,
1202+
threshold: trust_quorum_protocol::Threshold(request.threshold),
1203+
};
1204+
1205+
sa.trust_quorum()
1206+
.reconfigure(msg)
1207+
.await
1208+
.map_err(|e| HttpError::for_internal_error(e.to_string()))?;
1209+
1210+
Ok(HttpResponseUpdatedNoContent())
1211+
}
1212+
1213+
async fn trust_quorum_upgrade_from_lrtq(
1214+
request_context: RequestContext<Self::Context>,
1215+
body: TypedBody<TrustQuorumLrtqUpgradeRequest>,
1216+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
1217+
let sa = request_context.context();
1218+
let request = body.into_inner();
1219+
1220+
let msg = trust_quorum_protocol::LrtqUpgradeMsg {
1221+
rack_id: request.rack_id,
1222+
epoch: trust_quorum_protocol::Epoch(request.epoch),
1223+
members: request.members,
1224+
threshold: trust_quorum_protocol::Threshold(request.threshold),
1225+
};
1226+
1227+
sa.trust_quorum()
1228+
.upgrade_from_lrtq(msg)
1229+
.await
1230+
.map_err(|e| HttpError::for_internal_error(e.to_string()))?;
1231+
1232+
Ok(HttpResponseUpdatedNoContent())
1233+
}
1234+
1235+
async fn trust_quorum_commit(
1236+
request_context: RequestContext<Self::Context>,
1237+
body: TypedBody<TrustQuorumCommitRequest>,
1238+
) -> Result<HttpResponseOk<TrustQuorumCommitResponse>, HttpError> {
1239+
let sa = request_context.context();
1240+
let request = body.into_inner();
1241+
1242+
let status = sa
1243+
.trust_quorum()
1244+
.commit(
1245+
request.rack_id,
1246+
trust_quorum_protocol::Epoch(request.epoch),
1247+
)
1248+
.await
1249+
.map_err(|e| HttpError::for_internal_error(e.to_string()))?;
1250+
1251+
let response = match status {
1252+
trust_quorum::CommitStatus::Committed => {
1253+
TrustQuorumCommitResponse::Committed
1254+
}
1255+
trust_quorum::CommitStatus::Pending => {
1256+
TrustQuorumCommitResponse::Pending
1257+
}
1258+
};
1259+
1260+
Ok(HttpResponseOk(response))
1261+
}
1262+
1263+
async fn trust_quorum_coordinator_status(
1264+
request_context: RequestContext<Self::Context>,
1265+
) -> Result<HttpResponseOk<Option<TrustQuorumCoordinatorStatus>>, HttpError>
1266+
{
1267+
let sa = request_context.context();
1268+
1269+
let status = sa
1270+
.trust_quorum()
1271+
.coordinator_status()
1272+
.await
1273+
.map_err(|e| HttpError::for_internal_error(e.to_string()))?;
1274+
1275+
let response = status.map(|s| TrustQuorumCoordinatorStatus {
1276+
config: TrustQuorumConfiguration {
1277+
rack_id: s.config.rack_id,
1278+
epoch: s.config.epoch.0,
1279+
coordinator: s.config.coordinator,
1280+
members: s
1281+
.config
1282+
.members
1283+
.into_iter()
1284+
.map(|(id, digest)| (id, hex::encode(digest.0)))
1285+
.collect(),
1286+
threshold: s.config.threshold.0,
1287+
},
1288+
acked_prepares: s.acked_prepares,
1289+
});
1290+
1291+
Ok(HttpResponseOk(response))
1292+
}
1293+
1294+
async fn trust_quorum_prepare_and_commit(
1295+
request_context: RequestContext<Self::Context>,
1296+
body: TypedBody<TrustQuorumPrepareAndCommitRequest>,
1297+
) -> Result<HttpResponseOk<TrustQuorumCommitResponse>, HttpError> {
1298+
let sa = request_context.context();
1299+
let request = body.into_inner();
1300+
1301+
let bad_request = |msg: String| HttpError::for_bad_request(None, msg);
1302+
1303+
let parse_digest = |hex: &str| -> Result<[u8; 32], HttpError> {
1304+
let bytes = hex::decode(hex)
1305+
.map_err(|e| bad_request(format!("invalid hex: {e}")))?;
1306+
bytes.try_into().map_err(|v: Vec<u8>| {
1307+
bad_request(format!("digest must be 32 bytes, got {}", v.len()))
1308+
})
1309+
};
1310+
1311+
let members = request
1312+
.members
1313+
.into_iter()
1314+
.map(|(id, hex)| {
1315+
Ok((
1316+
id,
1317+
trust_quorum_protocol::Sha3_256Digest(parse_digest(&hex)?),
1318+
))
1319+
})
1320+
.collect::<Result<_, HttpError>>()?;
1321+
1322+
let encrypted_rack_secrets = request
1323+
.encrypted_rack_secrets
1324+
.map(
1325+
|ers| -> Result<
1326+
trust_quorum_protocol::EncryptedRackSecrets,
1327+
HttpError,
1328+
> {
1329+
let salt = parse_digest(&ers.salt)?;
1330+
let data = hex::decode(&ers.data).map_err(|e| {
1331+
bad_request(format!("invalid hex data: {e}"))
1332+
})?;
1333+
Ok(trust_quorum_protocol::EncryptedRackSecrets::new(
1334+
trust_quorum_protocol::Salt(salt),
1335+
data.into_boxed_slice(),
1336+
))
1337+
},
1338+
)
1339+
.transpose()?;
1340+
1341+
let config = trust_quorum_protocol::Configuration {
1342+
rack_id: request.rack_id,
1343+
epoch: trust_quorum_protocol::Epoch(request.epoch),
1344+
coordinator: request.coordinator,
1345+
members,
1346+
threshold: trust_quorum_protocol::Threshold(request.threshold),
1347+
encrypted_rack_secrets,
1348+
};
1349+
1350+
let status = sa
1351+
.trust_quorum()
1352+
.prepare_and_commit(config)
1353+
.await
1354+
.map_err(|e| HttpError::for_internal_error(e.to_string()))?;
1355+
1356+
let response = match status {
1357+
trust_quorum::CommitStatus::Committed => {
1358+
TrustQuorumCommitResponse::Committed
1359+
}
1360+
trust_quorum::CommitStatus::Pending => {
1361+
TrustQuorumCommitResponse::Pending
1362+
}
1363+
};
1364+
1365+
Ok(HttpResponseOk(response))
1366+
}
11811367
}

sled-agent/src/sim/http_entrypoints.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ use sled_agent_types::support_bundle::{
6767
SupportBundleMetadata, SupportBundlePathParam,
6868
SupportBundleTransferQueryParams,
6969
};
70+
use sled_agent_types::trust_quorum::{
71+
TrustQuorumCommitRequest, TrustQuorumCommitResponse,
72+
TrustQuorumCoordinatorStatus, TrustQuorumLrtqUpgradeRequest,
73+
TrustQuorumPrepareAndCommitRequest, TrustQuorumReconfigureRequest,
74+
};
7075
use sled_agent_types::zone_bundle::{
7176
BundleUtilization, CleanupContext, CleanupContextUpdate, CleanupCount,
7277
ZoneBundleFilter, ZoneBundleId, ZoneBundleMetadata, ZonePathParam,
@@ -922,6 +927,41 @@ impl SledAgentApi for SledAgentSimImpl {
922927
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
923928
Ok(HttpResponseUpdatedNoContent())
924929
}
930+
931+
async fn trust_quorum_reconfigure(
932+
_request_context: RequestContext<Self::Context>,
933+
_body: TypedBody<TrustQuorumReconfigureRequest>,
934+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
935+
method_unimplemented()
936+
}
937+
938+
async fn trust_quorum_upgrade_from_lrtq(
939+
_request_context: RequestContext<Self::Context>,
940+
_body: TypedBody<TrustQuorumLrtqUpgradeRequest>,
941+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
942+
method_unimplemented()
943+
}
944+
945+
async fn trust_quorum_commit(
946+
_request_context: RequestContext<Self::Context>,
947+
_body: TypedBody<TrustQuorumCommitRequest>,
948+
) -> Result<HttpResponseOk<TrustQuorumCommitResponse>, HttpError> {
949+
method_unimplemented()
950+
}
951+
952+
async fn trust_quorum_coordinator_status(
953+
_request_context: RequestContext<Self::Context>,
954+
) -> Result<HttpResponseOk<Option<TrustQuorumCoordinatorStatus>>, HttpError>
955+
{
956+
method_unimplemented()
957+
}
958+
959+
async fn trust_quorum_prepare_and_commit(
960+
_request_context: RequestContext<Self::Context>,
961+
_body: TypedBody<TrustQuorumPrepareAndCommitRequest>,
962+
) -> Result<HttpResponseOk<TrustQuorumCommitResponse>, HttpError> {
963+
method_unimplemented()
964+
}
925965
}
926966

927967
fn method_unimplemented<T>() -> Result<T, HttpError> {

sled-agent/src/sled_agent.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,9 @@ struct SledAgentInner {
370370
// A handle to the bootstore.
371371
bootstore: bootstore::NodeHandle,
372372

373+
// A handle to the trust quorum.
374+
trust_quorum: trust_quorum::NodeTaskHandle,
375+
373376
// A handle to the hardware monitor.
374377
hardware_monitor: HardwareMonitorHandle,
375378

@@ -683,6 +686,7 @@ impl SledAgent {
683686
rack_network_config,
684687
zone_bundler: long_running_task_handles.zone_bundler.clone(),
685688
bootstore: long_running_task_handles.bootstore.clone(),
689+
trust_quorum: long_running_task_handles.trust_quorum.clone(),
686690
hardware_monitor: long_running_task_handles
687691
.hardware_monitor
688692
.clone(),
@@ -1048,6 +1052,10 @@ impl SledAgent {
10481052
self.inner.bootstore.clone()
10491053
}
10501054

1055+
pub fn trust_quorum(&self) -> trust_quorum::NodeTaskHandle {
1056+
self.inner.trust_quorum.clone()
1057+
}
1058+
10511059
pub fn list_vpc_routes(&self) -> Vec<ResolvedVpcRouteState> {
10521060
self.inner.port_manager.vpc_routes_list()
10531061
}

sled-agent/types/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ pub mod rack_init;
2020
pub mod rack_ops;
2121
pub mod sled;
2222
pub mod support_bundle;
23+
pub mod trust_quorum;
2324
pub mod zone_bundle;
2425
pub mod zone_images;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Trust quorum types for the Sled Agent API.
6+
7+
pub use sled_agent_types_versions::latest::trust_quorum::*;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Version `ADD_TRUST_QUORUM` of the Sled Agent API.
6+
//!
7+
//! This version adds endpoints for trust quorum reconfiguration.
8+
9+
pub mod trust_quorum;

0 commit comments

Comments
 (0)