Skip to content

Commit 606eeee

Browse files
committed
bfd scaffolding
1 parent 9febaab commit 606eeee

File tree

14 files changed

+556
-12
lines changed

14 files changed

+556
-12
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/app/sagas/switch_port_settings_common.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 super::NexusActionContext;
26
use crate::app::map_switch_zone_addrs;
37
use crate::Nexus;

nexus/src/external_api/http_entrypoints.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ 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::views::SiloQuotas;
4544
use nexus_types::external_api::views::Utilization;
45+
use nexus_types::external_api::{shared::BfdStatus, views::SiloQuotas};
4646
use nexus_types::identity::AssetIdentityMetadata;
4747
use omicron_common::api::external::http_pagination::data_page_params_for;
4848
use omicron_common::api::external::http_pagination::marker_for_name;
@@ -270,6 +270,10 @@ pub(crate) fn external_api() -> NexusApiDescription {
270270
api.register(networking_bgp_announce_set_list)?;
271271
api.register(networking_bgp_announce_set_delete)?;
272272

273+
api.register(networking_bfd_enable)?;
274+
api.register(networking_bfd_disable)?;
275+
api.register(networking_bfd_status)?;
276+
273277
api.register(utilization_view)?;
274278

275279
// Fleet-wide API operations
@@ -3380,6 +3384,68 @@ async fn networking_bgp_announce_set_delete(
33803384
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
33813385
}
33823386

3387+
/// Enable a BFD session.
3388+
#[endpoint {
3389+
method = POST,
3390+
path = "/v1/system/networking/bfd-enable",
3391+
tags = ["system/networking"],
3392+
}]
3393+
async fn networking_bfd_enable(
3394+
rqctx: RequestContext<Arc<ServerContext>>,
3395+
session: TypedBody<params::BfdSessionEnable>,
3396+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
3397+
let apictx = rqctx.context();
3398+
let handler = async {
3399+
let nexus = &apictx.nexus;
3400+
let opctx = crate::context::op_context_for_external_api(&rqctx).await?;
3401+
opctx.authorize(authz::Action::ListChildren, &authz::FLEET).await?;
3402+
nexus.bfd_enable(&opctx, session.into_inner()).await?;
3403+
Ok(HttpResponseUpdatedNoContent {})
3404+
};
3405+
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
3406+
}
3407+
3408+
/// Disable a BFD session.
3409+
#[endpoint {
3410+
method = POST,
3411+
path = "/v1/system/networking/bfd-disable",
3412+
tags = ["system/networking"],
3413+
}]
3414+
async fn networking_bfd_disable(
3415+
rqctx: RequestContext<Arc<ServerContext>>,
3416+
session: TypedBody<params::BfdSessionDisable>,
3417+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
3418+
let apictx = rqctx.context();
3419+
let handler = async {
3420+
let nexus = &apictx.nexus;
3421+
let opctx = crate::context::op_context_for_external_api(&rqctx).await?;
3422+
opctx.authorize(authz::Action::ListChildren, &authz::FLEET).await?;
3423+
nexus.bfd_disable(&opctx, session.into_inner()).await?;
3424+
Ok(HttpResponseUpdatedNoContent {})
3425+
};
3426+
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
3427+
}
3428+
3429+
/// Get BFD status.
3430+
#[endpoint {
3431+
method = GET,
3432+
path = "/v1/system/networking/bfd-status",
3433+
tags = ["system/networking"],
3434+
}]
3435+
async fn networking_bfd_status(
3436+
rqctx: RequestContext<Arc<ServerContext>>,
3437+
) -> Result<HttpResponseOk<Vec<BfdStatus>>, HttpError> {
3438+
let apictx = rqctx.context();
3439+
let handler = async {
3440+
let nexus = &apictx.nexus;
3441+
let opctx = crate::context::op_context_for_external_api(&rqctx).await?;
3442+
opctx.authorize(authz::Action::ListChildren, &authz::FLEET).await?;
3443+
let status = nexus.bfd_status(&opctx).await?;
3444+
Ok(HttpResponseOk(status))
3445+
};
3446+
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
3447+
}
3448+
33833449
// Images
33843450

33853451
/// List images

nexus/tests/integration_tests/endpoints.rs

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

567+
pub const DEMO_BFD_STATUS_URL: &'static str =
568+
"/v1/system/networking/bfd-status";
569+
570+
pub const DEMO_BFD_ENABLE_URL: &'static str =
571+
"/v1/system/networking/bfd-enable";
572+
573+
pub const DEMO_BFD_DISABLE_URL: &'static str =
574+
"/v1/system/networking/bfd-disable";
575+
576+
pub static DEMO_BFD_ENABLE: Lazy<params::BfdSessionEnable> =
577+
Lazy::new(|| params::BfdSessionEnable {
578+
local: None,
579+
remote: "10.0.0.1".parse().unwrap(),
580+
detection_threshold: 3,
581+
required_rx: 1000000,
582+
switch: "switch0".parse().unwrap(),
583+
});
584+
585+
pub static DEMO_BFD_DISABLE: Lazy<params::BfdSessionDisable> =
586+
Lazy::new(|| params::BfdSessionDisable {
587+
remote: "10.0.0.1".parse().unwrap(),
588+
switch: "switch0".parse().unwrap(),
589+
});
590+
567591
// Project Images
568592
pub static DEMO_IMAGE_NAME: Lazy<Name> =
569593
Lazy::new(|| "demo-image".parse().unwrap());
@@ -2208,6 +2232,37 @@ pub static VERIFY_ENDPOINTS: Lazy<Vec<VerifyEndpoint>> = Lazy::new(|| {
22082232
],
22092233
},
22102234

2235+
VerifyEndpoint {
2236+
url: &DEMO_BFD_STATUS_URL,
2237+
visibility: Visibility::Public,
2238+
unprivileged_access: UnprivilegedAccess::None,
2239+
allowed_methods: vec![
2240+
AllowedMethod::GetNonexistent,
2241+
],
2242+
},
2243+
2244+
VerifyEndpoint {
2245+
url: &DEMO_BFD_ENABLE_URL,
2246+
visibility: Visibility::Public,
2247+
unprivileged_access: UnprivilegedAccess::None,
2248+
allowed_methods: vec![
2249+
AllowedMethod::Post(
2250+
serde_json::to_value(&*DEMO_BFD_ENABLE).unwrap()
2251+
)
2252+
],
2253+
},
2254+
2255+
VerifyEndpoint {
2256+
url: &DEMO_BFD_DISABLE_URL,
2257+
visibility: Visibility::Public,
2258+
unprivileged_access: UnprivilegedAccess::None,
2259+
allowed_methods: vec![
2260+
AllowedMethod::Post(
2261+
serde_json::to_value(&*DEMO_BFD_DISABLE).unwrap()
2262+
)
2263+
],
2264+
},
2265+
22112266
// Floating IPs
22122267
VerifyEndpoint {
22132268
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
@@ -159,6 +159,9 @@ networking_address_lot_block_list GET /v1/system/networking/address-
159159
networking_address_lot_create POST /v1/system/networking/address-lot
160160
networking_address_lot_delete DELETE /v1/system/networking/address-lot/{address_lot}
161161
networking_address_lot_list GET /v1/system/networking/address-lot
162+
networking_bfd_disable POST /v1/system/networking/bfd-disable
163+
networking_bfd_enable POST /v1/system/networking/bfd-enable
164+
networking_bfd_status GET /v1/system/networking/bfd-status
162165
networking_bgp_announce_set_create POST /v1/system/networking/bgp-announce
163166
networking_bgp_announce_set_delete DELETE /v1/system/networking/bgp-announce
164167
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
@@ -1755,6 +1755,39 @@ pub struct BgpStatusSelector {
17551755
pub name_or_id: NameOrId,
17561756
}
17571757

1758+
/// Information about a bidirectional forwarding detection (BFD) session.
1759+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
1760+
pub struct BfdSessionEnable {
1761+
/// Address the Oxide switch will listen on for BFD traffic. If `None` then
1762+
/// the unspecified address (0.0.0.0 or ::) is used.
1763+
pub local: Option<IpAddr>,
1764+
1765+
/// Address of the remote peer to establish a BFD session with.
1766+
pub remote: IpAddr,
1767+
1768+
/// The negotiated Control packet transmission interval, multiplied by this
1769+
/// variable, will be the Detection Time for this session (as seen by the
1770+
/// remote system)
1771+
pub detection_threshold: u8,
1772+
1773+
/// The minimum interval, in microseconds, between received BFD
1774+
/// Control packets that this system requires
1775+
pub required_rx: u64,
1776+
1777+
/// The switch to enable this session on. Must be `switch0` or `switch1`.
1778+
pub switch: Name,
1779+
}
1780+
1781+
/// Information needed to disable a BFD session
1782+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
1783+
pub struct BfdSessionDisable {
1784+
/// Address of the remote peer to disable a BFD session for.
1785+
pub remote: IpAddr,
1786+
1787+
/// The switch to enable this session on. Must be `switch0` or `switch1`.
1788+
pub switch: Name,
1789+
}
1790+
17581791
/// A set of addresses associated with a port configuration.
17591792
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
17601793
pub struct AddressConfig {

0 commit comments

Comments
 (0)