Skip to content

Commit 73050fb

Browse files
committed
add bgp_config_update endpoint
1 parent c1e08a6 commit 73050fb

13 files changed

Lines changed: 378 additions & 3 deletions

File tree

nexus/db-queries/src/db/datastore/bgp.rs

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use omicron_common::api::external;
2525
use omicron_common::api::external::http_pagination::PaginatedBy;
2626
use omicron_common::api::external::{
2727
CreateResult, DeleteResult, Error, ListResultVec, LookupResult, NameOrId,
28-
ResourceType,
28+
ResourceType, UpdateResult,
2929
};
3030
use ref_cast::RefCast;
3131
use sled_agent_types::early_networking::RouterPeerType;
@@ -225,6 +225,181 @@ impl DataStore {
225225
})
226226
}
227227

228+
pub async fn bgp_config_update(
229+
&self,
230+
opctx: &OpContext,
231+
sel: &networking::BgpConfigSelector,
232+
update: &networking::BgpConfigUpdate,
233+
) -> UpdateResult<BgpConfig> {
234+
use nexus_db_schema::schema::bgp_config;
235+
use nexus_db_schema::schema::bgp_config::dsl as bgp_config_dsl;
236+
use nexus_db_schema::schema::{
237+
bgp_announce_set, bgp_announce_set::dsl as announce_set_dsl,
238+
};
239+
240+
let err = OptionalError::new();
241+
let conn = self.pool_connection_authorized(opctx).await?;
242+
self.transaction_retry_wrapper("bgp_config_update")
243+
.transaction(&conn, |conn| {
244+
let err = err.clone();
245+
async move {
246+
let name_or_id = sel.name_or_id.clone();
247+
248+
// Look up the existing config
249+
let existing: BgpConfig = match name_or_id {
250+
NameOrId::Id(id) => bgp_config_dsl::bgp_config
251+
.filter(bgp_config::id.eq(id))
252+
.filter(bgp_config::time_deleted.is_null())
253+
.select(BgpConfig::as_select())
254+
.limit(1)
255+
.first_async::<BgpConfig>(&conn)
256+
.await
257+
.map_err(|e| {
258+
let msg = "failed to lookup bgp config by id";
259+
error!(opctx.log, "{msg}"; "error" => ?e);
260+
match e {
261+
diesel::result::Error::NotFound => err
262+
.bail(Error::not_found_by_id(
263+
ResourceType::BgpConfig,
264+
&id,
265+
)),
266+
_ => err.bail(Error::internal_error(msg)),
267+
}
268+
})?,
269+
NameOrId::Name(name) => bgp_config_dsl::bgp_config
270+
.filter(bgp_config::name.eq(name.to_string()))
271+
.filter(bgp_config::time_deleted.is_null())
272+
.select(BgpConfig::as_select())
273+
.limit(1)
274+
.first_async::<BgpConfig>(&conn)
275+
.await
276+
.map_err(|e| {
277+
let msg = "failed to lookup bgp config by name";
278+
error!(opctx.log, "{msg}"; "error" => ?e);
279+
match e {
280+
diesel::result::Error::NotFound => err
281+
.bail(Error::not_found_by_name(
282+
ResourceType::BgpConfig,
283+
&name,
284+
)),
285+
_ => err.bail(Error::internal_error(msg)),
286+
}
287+
})?,
288+
};
289+
290+
// Resolve bgp_announce_set_id if an update was requested
291+
let new_bgp_announce_set_id = match update
292+
.bgp_announce_set_id
293+
.clone()
294+
{
295+
None => existing.bgp_announce_set_id,
296+
Some(NameOrId::Name(name)) => {
297+
announce_set_dsl::bgp_announce_set
298+
.filter(
299+
bgp_announce_set::time_deleted.is_null(),
300+
)
301+
.filter(
302+
bgp_announce_set::name.eq(name.to_string()),
303+
)
304+
.select(bgp_announce_set::id)
305+
.limit(1)
306+
.first_async::<Uuid>(&conn)
307+
.await
308+
.map_err(|e| {
309+
let msg =
310+
"failed to lookup announce set by name";
311+
error!(opctx.log, "{msg}"; "error" => ?e);
312+
match e {
313+
diesel::result::Error::NotFound => err
314+
.bail(Error::not_found_by_name(
315+
ResourceType::BgpAnnounceSet,
316+
&name,
317+
)),
318+
_ => {
319+
err.bail(Error::internal_error(msg))
320+
}
321+
}
322+
})?
323+
}
324+
Some(NameOrId::Id(id)) => {
325+
announce_set_dsl::bgp_announce_set
326+
.filter(
327+
bgp_announce_set::time_deleted.is_null(),
328+
)
329+
.filter(bgp_announce_set::id.eq(id))
330+
.select(bgp_announce_set::id)
331+
.limit(1)
332+
.first_async::<Uuid>(&conn)
333+
.await
334+
.map_err(|e| {
335+
let msg =
336+
"failed to lookup announce set by id";
337+
error!(opctx.log, "{msg}"; "error" => ?e);
338+
match e {
339+
diesel::result::Error::NotFound => err
340+
.bail(Error::not_found_by_id(
341+
ResourceType::BgpAnnounceSet,
342+
&id,
343+
)),
344+
_ => {
345+
err.bail(Error::internal_error(msg))
346+
}
347+
}
348+
})?
349+
}
350+
};
351+
352+
let new_name = update
353+
.name
354+
.as_ref()
355+
.map(|n| n.to_string())
356+
.unwrap_or_else(|| existing.name().to_string());
357+
let new_description = update
358+
.description
359+
.clone()
360+
.unwrap_or_else(|| existing.description().to_string());
361+
let new_max_paths = update
362+
.max_paths
363+
.map(|m| m.as_u8())
364+
.unwrap_or(*existing.max_paths);
365+
366+
diesel::update(bgp_config_dsl::bgp_config)
367+
.filter(bgp_config_dsl::id.eq(existing.id()))
368+
.set((
369+
bgp_config_dsl::time_modified.eq(Utc::now()),
370+
bgp_config_dsl::name.eq(new_name),
371+
bgp_config_dsl::description.eq(new_description),
372+
bgp_config_dsl::bgp_announce_set_id
373+
.eq(new_bgp_announce_set_id),
374+
bgp_config_dsl::max_paths
375+
.eq(i16::from(new_max_paths)),
376+
))
377+
.returning(BgpConfig::as_returning())
378+
.get_result_async(&conn)
379+
.await
380+
.map_err(|e| {
381+
let msg = "bgp_config_update failed";
382+
error!(opctx.log, "{msg}"; "error" => ?e);
383+
err.bail(public_error_from_diesel(
384+
e,
385+
ErrorHandler::Server,
386+
))
387+
})
388+
}
389+
})
390+
.await
391+
.map_err(|e| {
392+
let msg = "bgp_config_update failed";
393+
if let Some(err) = err.take() {
394+
error!(opctx.log, "{msg}"; "error" => ?err);
395+
err
396+
} else {
397+
error!(opctx.log, "{msg}"; "error" => ?e);
398+
public_error_from_diesel(e, ErrorHandler::Server)
399+
}
400+
})
401+
}
402+
228403
pub async fn bgp_config_delete(
229404
&self,
230405
opctx: &OpContext,

nexus/external-api/output/nexus_tags.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ networking_bgp_announcement_list GET /v1/system/networking/bgp-anno
279279
networking_bgp_config_create POST /v1/system/networking/bgp
280280
networking_bgp_config_delete DELETE /v1/system/networking/bgp
281281
networking_bgp_config_list GET /v1/system/networking/bgp
282+
networking_bgp_config_update PUT /v1/system/networking/bgp
282283
networking_bgp_exported GET /v1/system/networking/bgp-exported
283284
networking_bgp_imported GET /v1/system/networking/bgp-imported
284285
networking_bgp_message_history GET /v1/system/networking/bgp-message-history

nexus/external-api/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ api_versions!([
8484
// | date-based version should be at the top of the list.
8585
// v
8686
// (next_yyyy_mm_dd_nn, IDENT),
87+
(2026_06_08_00, BGP_CONFIGURATION_UPDATE),
8788
(2026_06_03_00, DISK_BLOCK_SIZE_TYPE),
8889
(2026_05_20_00, ADD_CONTACT_SUPPORT_TO_UPDATE_STATUS),
8990
(2026_05_08_00, MANUAL_DISK_ADOPTION),
@@ -5421,6 +5422,24 @@ pub trait NexusExternalApi {
54215422
sel: Query<latest::networking::BgpConfigSelector>,
54225423
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
54235424

5425+
/// Update BGP configuration
5426+
///
5427+
/// Update the mutable fields of an existing BGP configuration. The `asn`
5428+
/// field is intentionally not updatable; changing the autonomous system
5429+
/// number requires creating a new BGP configuration object, since many
5430+
/// things are keyed off the ASN.
5431+
#[endpoint {
5432+
method = PUT,
5433+
path = "/v1/system/networking/bgp",
5434+
tags = ["system/networking"],
5435+
versions = VERSION_BGP_CONFIGURATION_UPDATE..,
5436+
}]
5437+
async fn networking_bgp_config_update(
5438+
rqctx: RequestContext<Self::Context>,
5439+
sel: Query<latest::networking::BgpConfigSelector>,
5440+
update: TypedBody<latest::networking::BgpConfigUpdate>,
5441+
) -> Result<HttpResponseOk<latest::networking::BgpConfig>, HttpError>;
5442+
54245443
/// Update BGP announce set
54255444
///
54265445
/// If the announce set exists, this endpoint replaces the existing announce

nexus/src/app/bgp.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use nexus_types::external_api::networking;
1010
use omicron_common::api::external::http_pagination::PaginatedBy;
1111
use omicron_common::api::external::{
1212
self, CreateResult, DeleteResult, ListResultVec, LookupResult, NameOrId,
13+
UpdateResult,
1314
};
1415
use slog_error_chain::InlineErrorChain;
1516

@@ -42,6 +43,21 @@ impl super::Nexus {
4243
self.db_datastore.bgp_config_list(opctx, pagparams).await
4344
}
4445

46+
pub async fn bgp_config_update(
47+
&self,
48+
opctx: &OpContext,
49+
sel: &networking::BgpConfigSelector,
50+
update: &networking::BgpConfigUpdate,
51+
) -> UpdateResult<BgpConfig> {
52+
opctx.authorize(authz::Action::Modify, &authz::FLEET).await?;
53+
let result =
54+
self.db_datastore.bgp_config_update(opctx, sel, update).await?;
55+
// Eagerly propagate changes via background task
56+
self.background_tasks
57+
.activate(&self.background_tasks.task_switch_port_settings_manager);
58+
Ok(result)
59+
}
60+
4561
pub async fn bgp_config_delete(
4662
&self,
4763
opctx: &OpContext,

nexus/src/external_api/http_entrypoints.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4491,6 +4491,20 @@ impl NexusExternalApi for NexusExternalApiImpl {
44914491
.await
44924492
}
44934493

4494+
async fn networking_bgp_config_update(
4495+
rqctx: RequestContext<ApiContext>,
4496+
sel: Query<networking::BgpConfigSelector>,
4497+
update: TypedBody<networking::BgpConfigUpdate>,
4498+
) -> Result<HttpResponseOk<networking::BgpConfig>, HttpError> {
4499+
audit_and_time(&rqctx, |opctx, nexus| async move {
4500+
let sel = sel.into_inner();
4501+
let update = update.into_inner();
4502+
let result = nexus.bgp_config_update(&opctx, &sel, &update).await?;
4503+
Ok(HttpResponseOk::<networking::BgpConfig>(result.try_into()?))
4504+
})
4505+
.await
4506+
}
4507+
44944508
async fn networking_bgp_announce_set_update(
44954509
rqctx: RequestContext<ApiContext>,
44964510
config: TypedBody<networking::BgpAnnounceSetCreate>,

nexus/tests/integration_tests/endpoints.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ use omicron_common::api::external::VpcFirewallRuleUpdateParams;
6969
use omicron_test_utils::certificates::CertificateChain;
7070
use semver::Version;
7171
use sled_agent_types::early_networking::BfdMode;
72+
use sled_agent_types::early_networking::MaxPathConfig;
7273
use sled_agent_types::early_networking::SwitchSlot;
7374
use std::collections::BTreeSet;
7475
use std::net::IpAddr;
@@ -1008,6 +1009,20 @@ pub static DEMO_BGP_CONFIG: LazyLock<networking::BgpConfigCreate> =
10081009
shaper: None,
10091010
max_paths: Default::default(),
10101011
});
1012+
pub static DEMO_BGP_CONFIG_UPDATE: LazyLock<networking::BgpConfigCreate> =
1013+
LazyLock::new(|| networking::BgpConfigCreate {
1014+
identity: IdentityMetadataCreateParams {
1015+
name: "as47".parse().unwrap(),
1016+
description: "BGP config for AS47".into(),
1017+
},
1018+
bgp_announce_set_id: NameOrId::Name("instances".parse().unwrap()),
1019+
asn: 47,
1020+
vrf: None,
1021+
checker: None,
1022+
shaper: None,
1023+
max_paths: MaxPathConfig::new(2).unwrap(),
1024+
});
1025+
10111026
pub const DEMO_BGP_ANNOUNCE_SET_URL: &'static str =
10121027
"/v1/system/networking/bgp-announce-set";
10131028
pub static DEMO_BGP_ANNOUNCE: LazyLock<networking::BgpAnnounceSetCreate> =
@@ -3416,6 +3431,9 @@ pub static VERIFY_ENDPOINTS: LazyLock<Vec<VerifyEndpoint>> = LazyLock::new(
34163431
serde_json::to_value(&*DEMO_BGP_CONFIG).unwrap(),
34173432
),
34183433
AllowedMethod::Get,
3434+
AllowedMethod::Put(
3435+
serde_json::to_value(&*DEMO_BGP_CONFIG_UPDATE).unwrap(),
3436+
),
34193437
AllowedMethod::Delete,
34203438
],
34213439
},
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
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+
pub mod networking;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
//! Networking types for the `BGP_CONFIGURATION_UPDATE` version.
6+
//!
7+
//! Changes in this version:
8+
//!
9+
//! * New [`BgpConfigUpdate`] type to allow updating a BGP configuration's
10+
//! `name`, `description`, `max_paths` and `bgp_announce_set_id` fields
11+
//! without deleting and recreating the object.
12+
13+
use omicron_common::api::external::Name;
14+
use omicron_common::api::external::NameOrId;
15+
use schemars::JsonSchema;
16+
use serde::{Deserialize, Serialize};
17+
use sled_agent_types_versions::v20::early_networking::MaxPathConfig;
18+
19+
/// Parameters for updating a BGP configuration.
20+
///
21+
/// The `asn` field is intentionally not updatable; changing the autonomous
22+
/// system number requires creating a new BGP configuration object, since many
23+
/// things are keyed off the ASN.
24+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
25+
pub struct BgpConfigUpdate {
26+
/// Update the name of this BGP configuration.
27+
pub name: Option<Name>,
28+
29+
/// Update the description of this BGP configuration.
30+
pub description: Option<String>,
31+
32+
/// Update the BGP announce set associated with this configuration.
33+
pub bgp_announce_set_id: Option<NameOrId>,
34+
35+
/// Update the maximum number of equal-cost paths.
36+
pub max_paths: Option<MaxPathConfig>,
37+
}

nexus/types/versions/src/latest.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,8 @@ pub mod networking {
307307

308308
pub use crate::v2026_05_07_00::networking::SwitchInterfaceConfig;
309309
pub use crate::v2026_05_07_00::networking::SwitchPortSettings;
310+
311+
pub use crate::v2026_06_08_00::networking::BgpConfigUpdate;
310312
}
311313

312314
pub mod oxql {

nexus/types/versions/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,5 @@ pub mod v2026_05_07_00;
8787
pub mod v2026_05_08_00;
8888
#[path = "add_contact_support_to_update_status/mod.rs"]
8989
pub mod v2026_05_20_00;
90+
#[path = "bgp_configuration_update/mod.rs"]
91+
pub mod v2026_06_08_00;

0 commit comments

Comments
 (0)