Skip to content

Commit 0e143b0

Browse files
authored
Add omdb command to view network config from the bootstore (#10225)
Closes #9931.
1 parent 5137bc3 commit 0e143b0

18 files changed

Lines changed: 273 additions & 44 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bootstore/src/schemes/v0/peer.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ impl NodeHandle {
221221
rx.await?
222222
}
223223

224+
/// Get the current contents of the watch channel containing the network
225+
/// config
226+
pub fn network_config_contents(&self) -> Option<NetworkConfig> {
227+
self.network_config_rx.borrow().clone()
228+
}
229+
224230
/// Subscribe to the watch channel containing the network config
225231
pub fn network_config_subscribe(
226232
&self,

common/src/address.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,13 @@ pub const MIN_PORT: u16 = u16::MIN;
216216
pub const DNS_PORT: u16 = 53;
217217
pub const DNS_HTTP_PORT: u16 = 5353;
218218
pub const SLED_AGENT_PORT: u16 = 12345;
219+
pub const BOOTSTRAP_AGENT_RACK_INIT_PORT: u16 = 12346;
220+
pub const BOOTSTORE_PORT: u16 = 12347;
219221
pub const REPO_DEPOT_PORT: u16 = 12348;
222+
pub const TRUST_QUORUM_PORT: u16 = 12349;
223+
224+
pub const BOOTSTRAP_AGENT_HTTP_PORT: u16 = 80;
225+
pub const BOOTSTRAP_AGENT_LOCKSTEP_PORT: u16 = 8080;
220226

221227
pub const COCKROACH_PORT: u16 = 32221;
222228
pub const COCKROACH_ADMIN_PORT: u16 = 32222;

dev-tools/omdb/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ vergen-gitcl.workspace = true
1515
anyhow.workspace = true
1616
async-bb8-diesel.workspace = true
1717
async-trait.workspace = true
18+
base64.workspace = true
19+
bootstrap-agent-lockstep-client.workspace = true
1820
bytes.workspace = true
1921
camino.workspace = true
2022
chrono.workspace = true

dev-tools/omdb/src/bin/omdb/sled_agent.rs

Lines changed: 118 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ use crate::Omdb;
88
use crate::check_allow_destructive::DestructiveOperationToken;
99
use crate::helpers::CONNECTION_OPTIONS_HEADING;
1010
use anyhow::Context;
11+
use anyhow::anyhow;
1112
use anyhow::bail;
13+
use base64::Engine;
14+
use bootstrap_agent_lockstep_client::types::ReplicatedNetworkConfig;
15+
use bootstrap_agent_lockstep_client::types::ReplicatedNetworkConfigContents;
1216
use clap::Args;
1317
use clap::Subcommand;
1418
use sled_agent_client::types::NodeStatus;
@@ -18,6 +22,8 @@ use sled_agent_client::types::OperatorSwitchZonePolicy;
1822
#[derive(Debug, Args)]
1923
pub struct SledAgentArgs {
2024
/// URL of the Sled internal API
25+
///
26+
/// This is typically port 12345 on a sled's underlay network IP address.
2127
#[clap(
2228
long,
2329
env = "OMDB_SLED_AGENT_URL",
@@ -26,6 +32,17 @@ pub struct SledAgentArgs {
2632
)]
2733
sled_agent_url: Option<String>,
2834

35+
/// URL of the sled bootstrap-agent-lockstep API
36+
///
37+
/// This is typically port 8080 on a sled's bootstrap network IP address.
38+
#[clap(
39+
long,
40+
env = "OMDB_BOOTSTRAP_LOCKSTEP_URL",
41+
global = true,
42+
help_heading = CONNECTION_OPTIONS_HEADING,
43+
)]
44+
bootstrap_lockstep_url: Option<String>,
45+
2946
#[command(subcommand)]
3047
command: SledAgentCommands,
3148
}
@@ -41,6 +58,10 @@ enum SledAgentCommands {
4158
#[clap(subcommand)]
4259
SwitchZonePolicy(SwitchZonePolicyCommands),
4360

61+
/// print information about the replicated network config
62+
#[clap(subcommand)]
63+
NetworkConfig(NetworkConfigCommands),
64+
4465
/// print information about the local bootstore node
4566
#[clap(subcommand)]
4667
Bootstore(BootstoreCommands),
@@ -71,6 +92,12 @@ enum SwitchZonePolicyCommands {
7192
DangerDangerDisable,
7293
}
7394

95+
#[derive(Debug, Subcommand)]
96+
enum NetworkConfigCommands {
97+
/// show the current contents of the replicated network config
98+
Show,
99+
}
100+
74101
#[derive(Debug, Subcommand)]
75102
enum BootstoreCommands {
76103
/// show the internal state of the local bootstore node
@@ -102,30 +129,50 @@ impl SledAgentArgs {
102129
omdb: &Omdb,
103130
log: &slog::Logger,
104131
) -> Result<(), anyhow::Error> {
105-
// This is a little goofy. The sled URL is required, but can come
106-
// from the environment, in which case it won't be on the command line.
107-
let Some(sled_agent_url) = &self.sled_agent_url else {
108-
bail!(
109-
"sled URL must be specified with --sled-agent-url or \
110-
OMDB_SLED_AGENT_URL"
111-
);
132+
// We require either a sled_agent_url or a bootstrap_lockstep_url, but
133+
// we don't know which one until we match on `self.command` below. Wrap
134+
// the conversion from URL-to-client in a closure so we only bail out if
135+
// we're missing a URL argument we actually need.
136+
137+
// Helper to create a sled-agent client.
138+
let make_sa_client = || -> anyhow::Result<_> {
139+
let Some(sled_agent_url) = &self.sled_agent_url else {
140+
bail!(
141+
"sled URL must be specified with --sled-agent-url or \
142+
OMDB_SLED_AGENT_URL"
143+
);
144+
};
145+
Ok(sled_agent_client::Client::new(sled_agent_url, log.clone()))
146+
};
147+
148+
// Helper to create a bootstrap-agent-lockstep client.
149+
let make_ba_lockstep_client = || -> anyhow::Result<_> {
150+
let Some(bootstrap_lockstep_url) = &self.bootstrap_lockstep_url
151+
else {
152+
bail!(
153+
"bootstrap lockstep URL must be specified with \
154+
--bootstrap-lockstep-url or OMDB_BOOTSTRAP_LOCKSTEP_URL"
155+
);
156+
};
157+
Ok(bootstrap_agent_lockstep_client::Client::new(
158+
bootstrap_lockstep_url,
159+
log.clone(),
160+
))
112161
};
113-
let client =
114-
sled_agent_client::Client::new(sled_agent_url, log.clone());
115162

116163
match &self.command {
117164
SledAgentCommands::Zones(ZoneCommands::List) => {
118-
cmd_zones_list(&client).await
165+
cmd_zones_list(&make_sa_client()?).await
119166
}
120167
SledAgentCommands::SwitchZonePolicy(
121168
SwitchZonePolicyCommands::Get,
122-
) => cmd_switch_zone_policy_get(&client).await,
169+
) => cmd_switch_zone_policy_get(&make_sa_client()?).await,
123170
SledAgentCommands::SwitchZonePolicy(
124171
SwitchZonePolicyCommands::Enable,
125172
) => {
126173
let token = omdb.check_allow_destructive()?;
127174
cmd_switch_zone_policy_put(
128-
&client,
175+
&make_sa_client()?,
129176
OperatorSwitchZonePolicy::StartIfSwitchPresent,
130177
token,
131178
)
@@ -136,21 +183,24 @@ impl SledAgentArgs {
136183
) => {
137184
let token = omdb.check_allow_destructive()?;
138185
cmd_switch_zone_policy_put(
139-
&client,
186+
&make_sa_client()?,
140187
OperatorSwitchZonePolicy::StopDespiteSwitchPresence,
141188
token,
142189
)
143190
.await
144191
}
192+
SledAgentCommands::NetworkConfig(NetworkConfigCommands::Show) => {
193+
cmd_network_config_show(&make_ba_lockstep_client()?).await
194+
}
145195
SledAgentCommands::Bootstore(BootstoreCommands::Status) => {
146-
cmd_bootstore_status(&client).await
196+
cmd_bootstore_status(&make_sa_client()?).await
147197
}
148198
SledAgentCommands::TrustQuorum(TrustQuorumCommands::Status) => {
149-
cmd_trust_quorum_status(&client).await
199+
cmd_trust_quorum_status(&make_sa_client()?).await
150200
}
151201
SledAgentCommands::TrustQuorum(
152202
TrustQuorumCommands::ProxyStatus(args),
153-
) => cmd_trust_quorum_proxy_status(&client, args).await,
203+
) => cmd_trust_quorum_proxy_status(&make_sa_client()?, args).await,
154204
}
155205
}
156206
}
@@ -212,6 +262,58 @@ async fn cmd_switch_zone_policy_put(
212262
cmd_switch_zone_policy_get(client).await
213263
}
214264

265+
/// Runs `omdb sled-agent network-config show`
266+
async fn cmd_network_config_show(
267+
client: &bootstrap_agent_lockstep_client::Client,
268+
) -> anyhow::Result<()> {
269+
let ReplicatedNetworkConfig { contents } = client
270+
.network_config_contents_for_debug()
271+
.await
272+
.context("failed to fetch network config contents")?
273+
.into_inner();
274+
let Some(contents) = contents else {
275+
println!(
276+
"no network contents available yet - \
277+
this is normal if RSS has not yet started"
278+
);
279+
return Ok(());
280+
};
281+
282+
let ReplicatedNetworkConfigContents { base64_blob, generation } = contents;
283+
println!("network config generation: {generation}");
284+
285+
let blob = {
286+
let mut output = Vec::new();
287+
match base64::engine::general_purpose::STANDARD
288+
.decode_vec(&base64_blob, &mut output)
289+
{
290+
Ok(()) => output,
291+
Err(err) => {
292+
println!("raw data (expected base64): {base64_blob}");
293+
return Err(
294+
anyhow!(err).context("failed to decode base64 blob")
295+
);
296+
}
297+
}
298+
};
299+
300+
let decoded: serde_json::Value = match serde_json::from_slice(&blob) {
301+
Ok(value) => value,
302+
Err(err) => {
303+
println!(
304+
"raw blob (expected JSON): {}",
305+
String::from_utf8_lossy(&blob)
306+
);
307+
return Err(anyhow!(err).context("failed to decode blob as JSON"));
308+
}
309+
};
310+
311+
println!("network config contents:");
312+
println!("{decoded:#}"); // `:#` format induces pretty-printing
313+
314+
Ok(())
315+
}
316+
215317
/// Runs `omdb sled-agent bootstore status`
216318
async fn cmd_bootstore_status(
217319
client: &sled_agent_client::Client,

dev-tools/omdb/tests/usage_errors.out

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,18 +1122,23 @@ Usage: omdb sled-agent [OPTIONS] <COMMAND>
11221122
Commands:
11231123
zones print information about zones
11241124
switch-zone-policy control the switch zone policy
1125+
network-config print information about the replicated network config
11251126
bootstore print information about the local bootstore node
11261127
trust-quorum print information about the local trust quorum node
11271128
help Print this message or the help of the given subcommand(s)
11281129

11291130
Options:
11301131
--log-level <LOG_LEVEL> log level filter [env: LOG_LEVEL=] [default: warn]
11311132
--color <COLOR> Color output [default: auto] [possible values: auto, always, never]
1132-
-h, --help Print help
1133+
-h, --help Print help (see more with '--help')
11331134

11341135
Connection Options:
1135-
--sled-agent-url <SLED_AGENT_URL> URL of the Sled internal API [env: OMDB_SLED_AGENT_URL=]
1136-
--dns-server <DNS_SERVER> [env: OMDB_DNS_SERVER=]
1136+
--sled-agent-url <SLED_AGENT_URL>
1137+
URL of the Sled internal API [env: OMDB_SLED_AGENT_URL=]
1138+
--bootstrap-lockstep-url <BOOTSTRAP_LOCKSTEP_URL>
1139+
URL of the sled bootstrap-agent-lockstep API [env: OMDB_BOOTSTRAP_LOCKSTEP_URL=]
1140+
--dns-server <DNS_SERVER>
1141+
[env: OMDB_DNS_SERVER=]
11371142

11381143
Safety Options:
11391144
-w, --destructive Allow potentially-destructive subcommands
@@ -1155,11 +1160,15 @@ Commands:
11551160
Options:
11561161
--log-level <LOG_LEVEL> log level filter [env: LOG_LEVEL=] [default: warn]
11571162
--color <COLOR> Color output [default: auto] [possible values: auto, always, never]
1158-
-h, --help Print help
1163+
-h, --help Print help (see more with '--help')
11591164

11601165
Connection Options:
1161-
--sled-agent-url <SLED_AGENT_URL> URL of the Sled internal API [env: OMDB_SLED_AGENT_URL=]
1162-
--dns-server <DNS_SERVER> [env: OMDB_DNS_SERVER=]
1166+
--sled-agent-url <SLED_AGENT_URL>
1167+
URL of the Sled internal API [env: OMDB_SLED_AGENT_URL=]
1168+
--bootstrap-lockstep-url <BOOTSTRAP_LOCKSTEP_URL>
1169+
URL of the sled bootstrap-agent-lockstep API [env: OMDB_BOOTSTRAP_LOCKSTEP_URL=]
1170+
--dns-server <DNS_SERVER>
1171+
[env: OMDB_DNS_SERVER=]
11631172

11641173
Safety Options:
11651174
-w, --destructive Allow potentially-destructive subcommands

openapi/bootstrap-agent-lockstep.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@
1010
"version": "0.0.1"
1111
},
1212
"paths": {
13+
"/debug/network-config-contents": {
14+
"get": {
15+
"summary": "Get the current contents of the network config kept in the replicated",
16+
"description": "bootstore.\n\nThis should ONLY be used for debugging (e.g., via `omdb`). Nexus, RSS, and sled-agent should never access this endpoint in production - the bootstore contents should be treated as \"write only\" from their point of view.",
17+
"operationId": "network_config_contents_for_debug",
18+
"responses": {
19+
"200": {
20+
"description": "successful operation",
21+
"content": {
22+
"application/json": {
23+
"schema": {
24+
"$ref": "#/components/schemas/ReplicatedNetworkConfig"
25+
}
26+
}
27+
}
28+
},
29+
"4XX": {
30+
"$ref": "#/components/responses/Error"
31+
},
32+
"5XX": {
33+
"$ref": "#/components/responses/Error"
34+
}
35+
}
36+
}
37+
},
1338
"/rack-initialize": {
1439
"get": {
1540
"summary": "Get the current status of rack initialization or reset.",
@@ -1205,6 +1230,39 @@
12051230
"user_password_hash"
12061231
]
12071232
},
1233+
"ReplicatedNetworkConfig": {
1234+
"description": "Wrapper for optional contents of the replicated network config.",
1235+
"type": "object",
1236+
"properties": {
1237+
"contents": {
1238+
"nullable": true,
1239+
"allOf": [
1240+
{
1241+
"$ref": "#/components/schemas/ReplicatedNetworkConfigContents"
1242+
}
1243+
]
1244+
}
1245+
}
1246+
},
1247+
"ReplicatedNetworkConfigContents": {
1248+
"description": "Contents of the replicated network config.\n\nAnalogous to the bootstore `NetworkConfig` type.",
1249+
"type": "object",
1250+
"properties": {
1251+
"base64_blob": {
1252+
"description": "The `NetworkConfig` arbitrary blob, encoded using the standard base64 alphabet.\n\nCurrently, the contents are expected to be JSON, but serialization/deserialization of the contents is performed outside the replication engine, which just deals with a binary blob.",
1253+
"type": "string"
1254+
},
1255+
"generation": {
1256+
"type": "integer",
1257+
"format": "uint64",
1258+
"minimum": 0
1259+
}
1260+
},
1261+
"required": [
1262+
"base64_blob",
1263+
"generation"
1264+
]
1265+
},
12081266
"RouteConfig": {
12091267
"type": "object",
12101268
"properties": {

0 commit comments

Comments
 (0)