Skip to content

Commit c8df023

Browse files
committed
bfd scaffolding
1 parent c2b90bc commit c8df023

File tree

13 files changed

+552
-11
lines changed

13 files changed

+552
-11
lines changed

nexus/src/app/bfd.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
use std::{
6+
net::{IpAddr, Ipv4Addr},
7+
sync::Arc,
8+
};
9+
10+
use crate::external_api::params;
11+
use mg_admin_client::types::{AddBfdPeerRequest, PeerState};
12+
use nexus_db_queries::context::OpContext;
13+
use nexus_types::external_api::shared::{BfdState, BfdStatus};
14+
use omicron_common::api::{
15+
external::{Error, Name},
16+
internal::shared::{ParseSwitchLocationError, SwitchLocation},
17+
};
18+
19+
impl super::Nexus {
20+
fn mg_client_for_switch_name(
21+
&self,
22+
switch: &Name,
23+
) -> Result<Arc<mg_admin_client::Client>, Error> {
24+
let switch_location: SwitchLocation = switch.as_str().parse().map_err(
25+
|_: ParseSwitchLocationError| {
26+
Error::invalid_value("switch", "must be switch0 or switch 1")
27+
},
28+
)?;
29+
30+
self.mg_client_for_switch_location(switch_location)
31+
}
32+
33+
fn mg_client_for_switch_location(
34+
&self,
35+
switch: SwitchLocation,
36+
) -> Result<Arc<mg_admin_client::Client>, Error> {
37+
let mg_client: Arc<mg_admin_client::Client> = self
38+
.mg_clients
39+
.get(&switch)
40+
.ok_or_else(|| {
41+
Error::not_found_by_name(
42+
omicron_common::api::external::ResourceType::Switch,
43+
&switch.to_string().parse().unwrap(),
44+
)
45+
})?
46+
.clone();
47+
48+
Ok(mg_client)
49+
}
50+
51+
pub async fn bfd_enable(
52+
&self,
53+
_opctx: &OpContext,
54+
session: params::BfdSessionEnable,
55+
) -> Result<(), Error> {
56+
let mg_client = self.mg_client_for_switch_name(&session.switch)?;
57+
58+
mg_client
59+
.inner
60+
.add_bfd_peer(&AddBfdPeerRequest {
61+
detection_threshold: session.detection_threshold,
62+
listen: session.local.unwrap_or(Ipv4Addr::UNSPECIFIED.into()),
63+
peer: session.remote,
64+
required_rx: session.required_rx,
65+
})
66+
.await
67+
.map_err(|e| {
68+
Error::internal_error(&format!("maghemite bfd enable: {e}"))
69+
})?;
70+
71+
Ok(())
72+
}
73+
74+
pub async fn bfd_disable(
75+
&self,
76+
_opctx: &OpContext,
77+
session: params::BfdSessionDisable,
78+
) -> Result<(), Error> {
79+
let mg_client = self.mg_client_for_switch_name(&session.switch)?;
80+
81+
mg_client.inner.remove_bfd_peer(&session.remote).await.map_err(
82+
|e| Error::internal_error(&format!("maghemite bfd disable: {e}")),
83+
)?;
84+
85+
Ok(())
86+
}
87+
88+
pub async fn bfd_status(
89+
&self,
90+
_opctx: &OpContext,
91+
) -> Result<Vec<BfdStatus>, Error> {
92+
let mut result = Vec::new();
93+
for s in &[SwitchLocation::Switch0, SwitchLocation::Switch1] {
94+
let mg_client = self.mg_client_for_switch_location(*s)?;
95+
let status = mg_client
96+
.inner
97+
.get_bfd_peers()
98+
.await
99+
.map_err(|e| {
100+
Error::internal_error(&format!(
101+
"maghemite get bfd peers: {e}"
102+
))
103+
})?
104+
.into_inner();
105+
106+
for (addr, state) in status.iter() {
107+
let addr: IpAddr = addr.parse().unwrap();
108+
result.push(BfdStatus {
109+
peer: addr,
110+
state: match state {
111+
PeerState::Up => BfdState::Up,
112+
PeerState::Down => BfdState::Down,
113+
PeerState::Init => BfdState::Init,
114+
PeerState::AdminDown => BfdState::AdminDown,
115+
},
116+
switch: s.to_string().parse().unwrap(),
117+
})
118+
}
119+
}
120+
Ok(result)
121+
}
122+
}

nexus/src/app/bgp.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +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+
15
use crate::app::authz;
26
use crate::external_api::params;
37
use nexus_db_model::{BgpAnnounceSet, BgpAnnouncement, BgpConfig};

nexus/src/app/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use uuid::Uuid;
3535
// by resource.
3636
mod address_lot;
3737
pub(crate) mod background;
38+
mod bfd;
3839
mod bgp;
3940
mod certificate;
4041
mod deployment;

nexus/src/external_api/http_entrypoints.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use nexus_db_queries::db::identity::Resource;
4141
use nexus_db_queries::db::lookup::ImageLookup;
4242
use nexus_db_queries::db::lookup::ImageParentLookup;
4343
use nexus_db_queries::db::model::Name;
44+
use nexus_types::external_api::shared::BfdStatus;
4445
use omicron_common::api::external::http_pagination::data_page_params_for;
4546
use omicron_common::api::external::http_pagination::marker_for_name;
4647
use omicron_common::api::external::http_pagination::marker_for_name_or_id;
@@ -273,6 +274,10 @@ pub(crate) fn external_api() -> NexusApiDescription {
273274
api.register(networking_bgp_announce_set_list)?;
274275
api.register(networking_bgp_announce_set_delete)?;
275276

277+
api.register(networking_bfd_enable)?;
278+
api.register(networking_bfd_disable)?;
279+
api.register(networking_bfd_status)?;
280+
276281
api.register(utilization_view)?;
277282

278283
// Fleet-wide API operations
@@ -3489,6 +3494,68 @@ async fn networking_bgp_announce_set_delete(
34893494
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
34903495
}
34913496

3497+
/// Enable a BFD session.
3498+
#[endpoint {
3499+
method = POST,
3500+
path = "/v1/system/networking/bfd-enable",
3501+
tags = ["system/networking"],
3502+
}]
3503+
async fn networking_bfd_enable(
3504+
rqctx: RequestContext<Arc<ServerContext>>,
3505+
session: TypedBody<params::BfdSessionEnable>,
3506+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
3507+
let apictx = rqctx.context();
3508+
let handler = async {
3509+
let nexus = &apictx.nexus;
3510+
let opctx = crate::context::op_context_for_external_api(&rqctx).await?;
3511+
opctx.authorize(authz::Action::ListChildren, &authz::FLEET).await?;
3512+
nexus.bfd_enable(&opctx, session.into_inner()).await?;
3513+
Ok(HttpResponseUpdatedNoContent {})
3514+
};
3515+
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
3516+
}
3517+
3518+
/// Disable a BFD session.
3519+
#[endpoint {
3520+
method = POST,
3521+
path = "/v1/system/networking/bfd-disable",
3522+
tags = ["system/networking"],
3523+
}]
3524+
async fn networking_bfd_disable(
3525+
rqctx: RequestContext<Arc<ServerContext>>,
3526+
session: TypedBody<params::BfdSessionDisable>,
3527+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
3528+
let apictx = rqctx.context();
3529+
let handler = async {
3530+
let nexus = &apictx.nexus;
3531+
let opctx = crate::context::op_context_for_external_api(&rqctx).await?;
3532+
opctx.authorize(authz::Action::ListChildren, &authz::FLEET).await?;
3533+
nexus.bfd_disable(&opctx, session.into_inner()).await?;
3534+
Ok(HttpResponseUpdatedNoContent {})
3535+
};
3536+
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
3537+
}
3538+
3539+
/// Get BFD status.
3540+
#[endpoint {
3541+
method = GET,
3542+
path = "/v1/system/networking/bfd-status",
3543+
tags = ["system/networking"],
3544+
}]
3545+
async fn networking_bfd_status(
3546+
rqctx: RequestContext<Arc<ServerContext>>,
3547+
) -> Result<HttpResponseOk<Vec<BfdStatus>>, HttpError> {
3548+
let apictx = rqctx.context();
3549+
let handler = async {
3550+
let nexus = &apictx.nexus;
3551+
let opctx = crate::context::op_context_for_external_api(&rqctx).await?;
3552+
opctx.authorize(authz::Action::ListChildren, &authz::FLEET).await?;
3553+
let status = nexus.bfd_status(&opctx).await?;
3554+
Ok(HttpResponseOk(status))
3555+
};
3556+
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
3557+
}
3558+
34923559
// Images
34933560

34943561
/// List images

nexus/tests/integration_tests/endpoints.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,30 @@ pub const DEMO_BGP_STATUS_URL: &'static str =
571571
pub const DEMO_BGP_ROUTES_IPV4_URL: &'static str =
572572
"/v1/system/networking/bgp-routes-ipv4?asn=47";
573573

574+
pub const DEMO_BFD_STATUS_URL: &'static str =
575+
"/v1/system/networking/bfd-status";
576+
577+
pub const DEMO_BFD_ENABLE_URL: &'static str =
578+
"/v1/system/networking/bfd-enable";
579+
580+
pub const DEMO_BFD_DISABLE_URL: &'static str =
581+
"/v1/system/networking/bfd-disable";
582+
583+
pub static DEMO_BFD_ENABLE: Lazy<params::BfdSessionEnable> =
584+
Lazy::new(|| params::BfdSessionEnable {
585+
local: None,
586+
remote: "10.0.0.1".parse().unwrap(),
587+
detection_threshold: 3,
588+
required_rx: 1000000,
589+
switch: "switch0".parse().unwrap(),
590+
});
591+
592+
pub static DEMO_BFD_DISABLE: Lazy<params::BfdSessionDisable> =
593+
Lazy::new(|| params::BfdSessionDisable {
594+
remote: "10.0.0.1".parse().unwrap(),
595+
switch: "switch0".parse().unwrap(),
596+
});
597+
574598
// Project Images
575599
pub static DEMO_IMAGE_NAME: Lazy<Name> =
576600
Lazy::new(|| "demo-image".parse().unwrap());
@@ -2189,6 +2213,37 @@ pub static VERIFY_ENDPOINTS: Lazy<Vec<VerifyEndpoint>> = Lazy::new(|| {
21892213
],
21902214
},
21912215

2216+
VerifyEndpoint {
2217+
url: &DEMO_BFD_STATUS_URL,
2218+
visibility: Visibility::Public,
2219+
unprivileged_access: UnprivilegedAccess::None,
2220+
allowed_methods: vec![
2221+
AllowedMethod::GetNonexistent,
2222+
],
2223+
},
2224+
2225+
VerifyEndpoint {
2226+
url: &DEMO_BFD_ENABLE_URL,
2227+
visibility: Visibility::Public,
2228+
unprivileged_access: UnprivilegedAccess::None,
2229+
allowed_methods: vec![
2230+
AllowedMethod::Post(
2231+
serde_json::to_value(&*DEMO_BFD_ENABLE).unwrap()
2232+
)
2233+
],
2234+
},
2235+
2236+
VerifyEndpoint {
2237+
url: &DEMO_BFD_DISABLE_URL,
2238+
visibility: Visibility::Public,
2239+
unprivileged_access: UnprivilegedAccess::None,
2240+
allowed_methods: vec![
2241+
AllowedMethod::Post(
2242+
serde_json::to_value(&*DEMO_BFD_DISABLE).unwrap()
2243+
)
2244+
],
2245+
},
2246+
21922247
// Floating IPs
21932248
VerifyEndpoint {
21942249
url: &DEMO_PROJECT_URL_FIPS,

nexus/tests/output/nexus_tags.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ networking_address_lot_block_list GET /v1/system/networking/address-
163163
networking_address_lot_create POST /v1/system/networking/address-lot
164164
networking_address_lot_delete DELETE /v1/system/networking/address-lot/{address_lot}
165165
networking_address_lot_list GET /v1/system/networking/address-lot
166+
networking_bfd_disable POST /v1/system/networking/bfd-disable
167+
networking_bfd_enable POST /v1/system/networking/bfd-enable
168+
networking_bfd_status GET /v1/system/networking/bfd-status
166169
networking_bgp_announce_set_create POST /v1/system/networking/bgp-announce
167170
networking_bgp_announce_set_delete DELETE /v1/system/networking/bgp-announce
168171
networking_bgp_announce_set_list GET /v1/system/networking/bgp-announce

nexus/types/src/external_api/params.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,6 +1788,39 @@ pub struct BgpStatusSelector {
17881788
pub name_or_id: NameOrId,
17891789
}
17901790

1791+
/// Information about a bidirectional forwarding detection (BFD) session.
1792+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
1793+
pub struct BfdSessionEnable {
1794+
/// Address the Oxide switch will listen on for BFD traffic. If `None` then
1795+
/// the unspecified address (0.0.0.0 or ::) is used.
1796+
pub local: Option<IpAddr>,
1797+
1798+
/// Address of the remote peer to establish a BFD session with.
1799+
pub remote: IpAddr,
1800+
1801+
/// The negotiated Control packet transmission interval, multiplied by this
1802+
/// variable, will be the Detection Time for this session (as seen by the
1803+
/// remote system)
1804+
pub detection_threshold: u8,
1805+
1806+
/// The minimum interval, in microseconds, between received BFD
1807+
/// Control packets that this system requires
1808+
pub required_rx: u64,
1809+
1810+
/// The switch to enable this session on. Must be `switch0` or `switch1`.
1811+
pub switch: Name,
1812+
}
1813+
1814+
/// Information needed to disable a BFD session
1815+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
1816+
pub struct BfdSessionDisable {
1817+
/// Address of the remote peer to disable a BFD session for.
1818+
pub remote: IpAddr,
1819+
1820+
/// The switch to enable this session on. Must be `switch0` or `switch1`.
1821+
pub switch: Name,
1822+
}
1823+
17911824
/// A set of addresses associated with a port configuration.
17921825
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
17931826
pub struct AddressConfig {

0 commit comments

Comments
 (0)