@@ -8,7 +8,11 @@ use crate::Omdb;
88use crate :: check_allow_destructive:: DestructiveOperationToken ;
99use crate :: helpers:: CONNECTION_OPTIONS_HEADING ;
1010use anyhow:: Context ;
11+ use anyhow:: anyhow;
1112use anyhow:: bail;
13+ use base64:: Engine ;
14+ use bootstrap_agent_lockstep_client:: types:: ReplicatedNetworkConfig ;
15+ use bootstrap_agent_lockstep_client:: types:: ReplicatedNetworkConfigContents ;
1216use clap:: Args ;
1317use clap:: Subcommand ;
1418use sled_agent_client:: types:: NodeStatus ;
@@ -18,6 +22,8 @@ use sled_agent_client::types::OperatorSwitchZonePolicy;
1822#[ derive( Debug , Args ) ]
1923pub 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 ) ]
75102enum 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`
216318async fn cmd_bootstore_status (
217319 client : & sled_agent_client:: Client ,
0 commit comments