From b78fcefdc4fd5b5a192299f6e0336ded215a1100 Mon Sep 17 00:00:00 2001 From: nicolassk Date: Wed, 10 Jun 2026 16:17:23 -0300 Subject: [PATCH 1/5] add bgp_config_update endpoint --- nexus/db-queries/src/db/datastore/bgp.rs | 134 +++++++++++++++++- nexus/external-api/output/nexus_tags.txt | 1 + nexus/external-api/src/lib.rs | 19 +++ nexus/src/app/bgp.rs | 16 +++ nexus/src/external_api/http_entrypoints.rs | 14 ++ nexus/tests/integration_tests/endpoints.rs | 18 +++ .../src/bgp_configuration_update/mod.rs | 5 + .../bgp_configuration_update/networking.rs | 37 +++++ nexus/types/versions/src/latest.rs | 2 + nexus/types/versions/src/lib.rs | 2 + .../nexus-2026060800.0.0-f1db6e.json.gitstub | 1 + ....json => nexus-2026061000.0.0-6e3e54.json} | 87 +++++++++++- openapi/nexus/nexus-latest.json | 2 +- 13 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 nexus/types/versions/src/bgp_configuration_update/mod.rs create mode 100644 nexus/types/versions/src/bgp_configuration_update/networking.rs create mode 100644 openapi/nexus/nexus-2026060800.0.0-f1db6e.json.gitstub rename openapi/nexus/{nexus-2026060800.0.0-f1db6e.json => nexus-2026061000.0.0-6e3e54.json} (99%) diff --git a/nexus/db-queries/src/db/datastore/bgp.rs b/nexus/db-queries/src/db/datastore/bgp.rs index 2f09214b349..ae2ae70f1f2 100644 --- a/nexus/db-queries/src/db/datastore/bgp.rs +++ b/nexus/db-queries/src/db/datastore/bgp.rs @@ -25,7 +25,7 @@ use omicron_common::api::external; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ CreateResult, DeleteResult, Error, ListResultVec, LookupResult, NameOrId, - ResourceType, + ResourceType, UpdateResult, }; use ref_cast::RefCast; use sled_agent_types::early_networking::RouterPeerType; @@ -225,6 +225,138 @@ impl DataStore { }) } + pub async fn bgp_config_update( + &self, + opctx: &OpContext, + sel: &networking::BgpConfigSelector, + update: &networking::BgpConfigUpdate, + ) -> UpdateResult { + use nexus_db_schema::schema::bgp_config; + use nexus_db_schema::schema::bgp_config::dsl as bgp_config_dsl; + use nexus_db_schema::schema::{ + bgp_announce_set, bgp_announce_set::dsl as announce_set_dsl, + }; + + let err = OptionalError::new(); + let transaction = async |conn| { + // Look up the existing config + let (query, not_found_err, msg) = match &sel.name_or_id { + NameOrId::Id(id) => ( + bgp_config_dsl::bgp_config + .filter(bgp_config::id.eq(*id)) + .into_boxed(), + Error::not_found_by_id(ResourceType::BgpConfig, id), + "failed to lookup bgp config by id", + ), + NameOrId::Name(name) => ( + bgp_config_dsl::bgp_config + .filter(bgp_config::name.eq(name.to_string())) + .into_boxed(), + Error::not_found_by_name(ResourceType::BgpConfig, name), + "failed to lookup bgp config by name", + ), + }; + + let lookup_err = |e, not_found_err, msg| { + error!(opctx.log, "{msg}"; "error" => ?e); + match e { + diesel::result::Error::NotFound => err.bail(not_found_err), + _ => err.bail(Error::internal_error(msg)), + } + }; + + let existing: BgpConfig = query + .filter(bgp_config::time_deleted.is_null()) + .select(BgpConfig::as_select()) + .limit(1) + .first_async::(&conn) + .await + .map_err(|e| lookup_err(e, not_found_err, msg))?; + + // Resolve bgp_announce_set_id if an update was requested + let new_bgp_announce_set_id = match &update.bgp_announce_set_id { + None => existing.bgp_announce_set_id, + Some(name_or_id) => { + let (query, not_found_err, msg) = match name_or_id { + NameOrId::Id(id) => ( + announce_set_dsl::bgp_announce_set + .filter(bgp_announce_set::id.eq(*id)) + .into_boxed(), + Error::not_found_by_id( + ResourceType::BgpAnnounceSet, + id, + ), + "failed to lookup announce set by id", + ), + NameOrId::Name(name) => ( + announce_set_dsl::bgp_announce_set + .filter( + bgp_announce_set::name.eq(name.to_string()), + ) + .into_boxed(), + Error::not_found_by_name( + ResourceType::BgpAnnounceSet, + name, + ), + "failed to lookup announce set by name", + ), + }; + query + .filter(bgp_announce_set::time_deleted.is_null()) + .select(bgp_announce_set::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|e| lookup_err(e, not_found_err, msg))? + } + }; + + let new_name = + update.name.as_ref().unwrap_or(existing.name()).to_string(); + let new_description = update + .description + .as_deref() + .unwrap_or(existing.description()) + .to_string(); + let new_max_paths = + update.max_paths.map_or(*existing.max_paths, |m| m.as_u8()); + + diesel::update(bgp_config_dsl::bgp_config) + .filter(bgp_config_dsl::id.eq(existing.id())) + .set(( + bgp_config_dsl::time_modified.eq(Utc::now()), + bgp_config_dsl::name.eq(new_name), + bgp_config_dsl::description.eq(new_description), + bgp_config_dsl::bgp_announce_set_id + .eq(new_bgp_announce_set_id), + bgp_config_dsl::max_paths.eq(i16::from(new_max_paths)), + )) + .returning(BgpConfig::as_returning()) + .get_result_async(&conn) + .await + .map_err(|e| { + let msg = "bgp_config_update failed"; + error!(opctx.log, "{msg}"; "error" => ?e); + err.bail(public_error_from_diesel(e, ErrorHandler::Server)) + }) + }; + + let conn = self.pool_connection_authorized(opctx).await?; + self.transaction_retry_wrapper("bgp_config_update") + .transaction(&conn, transaction) + .await + .map_err(|e| { + let msg = "bgp_config_update failed"; + if let Some(err) = err.take() { + error!(opctx.log, "{msg}"; "error" => ?err); + err + } else { + error!(opctx.log, "{msg}"; "error" => ?e); + public_error_from_diesel(e, ErrorHandler::Server) + } + }) + } + pub async fn bgp_config_delete( &self, opctx: &OpContext, diff --git a/nexus/external-api/output/nexus_tags.txt b/nexus/external-api/output/nexus_tags.txt index 20f7c265064..0f7057078fc 100644 --- a/nexus/external-api/output/nexus_tags.txt +++ b/nexus/external-api/output/nexus_tags.txt @@ -279,6 +279,7 @@ networking_bgp_announcement_list GET /v1/system/networking/bgp-anno networking_bgp_config_create POST /v1/system/networking/bgp networking_bgp_config_delete DELETE /v1/system/networking/bgp networking_bgp_config_list GET /v1/system/networking/bgp +networking_bgp_config_update PUT /v1/system/networking/bgp networking_bgp_exported GET /v1/system/networking/bgp-exported networking_bgp_imported GET /v1/system/networking/bgp-imported networking_bgp_message_history GET /v1/system/networking/bgp-message-history diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index 2c5f1952ab9..47ac25c15bf 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -86,6 +86,7 @@ api_versions!([ // | date-based version should be at the top of the list. // v // (next_yyyy_mm_dd_nn, IDENT), + (2026_06_10_00, BGP_CONFIGURATION_UPDATE), (2026_06_08_00, INSTANCE_CPU_TYPE_TURIN_V2), (2026_06_05_00, EXTERNAL_JUMBO_FRAMES), (2026_06_04_00, IMAGE_BLOCK_SIZE_TYPE), @@ -5814,6 +5815,24 @@ pub trait NexusExternalApi { sel: Query, ) -> Result; + /// Update BGP configuration + /// + /// Update the mutable fields of an existing BGP configuration. The `asn` + /// field is intentionally not updatable; changing the autonomous system + /// number requires creating a new BGP configuration object, since many + /// things are keyed off the ASN. + #[endpoint { + method = PUT, + path = "/v1/system/networking/bgp", + tags = ["system/networking"], + versions = VERSION_BGP_CONFIGURATION_UPDATE.., + }] + async fn networking_bgp_config_update( + rqctx: RequestContext, + sel: Query, + update: TypedBody, + ) -> Result, HttpError>; + /// Update BGP announce set /// /// If the announce set exists, this endpoint replaces the existing announce diff --git a/nexus/src/app/bgp.rs b/nexus/src/app/bgp.rs index 0ca95b2f294..0ab5a7fc8d1 100644 --- a/nexus/src/app/bgp.rs +++ b/nexus/src/app/bgp.rs @@ -10,6 +10,7 @@ use nexus_types::external_api::networking; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ self, CreateResult, DeleteResult, ListResultVec, LookupResult, NameOrId, + UpdateResult, }; use slog_error_chain::InlineErrorChain; @@ -42,6 +43,21 @@ impl super::Nexus { self.db_datastore.bgp_config_list(opctx, pagparams).await } + pub async fn bgp_config_update( + &self, + opctx: &OpContext, + sel: &networking::BgpConfigSelector, + update: &networking::BgpConfigUpdate, + ) -> UpdateResult { + opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; + let result = + self.db_datastore.bgp_config_update(opctx, sel, update).await?; + // Eagerly propagate changes via background task + self.background_tasks + .activate(&self.background_tasks.task_switch_port_settings_manager); + Ok(result) + } + pub async fn bgp_config_delete( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index e7287a53cb0..776325161f3 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -4532,6 +4532,20 @@ impl NexusExternalApi for NexusExternalApiImpl { .await } + async fn networking_bgp_config_update( + rqctx: RequestContext, + sel: Query, + update: TypedBody, + ) -> Result, HttpError> { + audit_and_time(&rqctx, |opctx, nexus| async move { + let sel = sel.into_inner(); + let update = update.into_inner(); + let result = nexus.bgp_config_update(&opctx, &sel, &update).await?; + Ok(HttpResponseOk::(result.try_into()?)) + }) + .await + } + async fn networking_bgp_announce_set_update( rqctx: RequestContext, config: TypedBody, diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index e73ac1cb1b4..2e9bc5f5972 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -70,6 +70,7 @@ use omicron_common::api::external::VpcFirewallRuleUpdateParams; use omicron_test_utils::certificates::CertificateChain; use semver::Version; use sled_agent_types::early_networking::BfdMode; +use sled_agent_types::early_networking::MaxPathConfig; use sled_agent_types::early_networking::SwitchSlot; use std::collections::BTreeSet; use std::net::IpAddr; @@ -1014,6 +1015,20 @@ pub static DEMO_BGP_CONFIG: LazyLock = shaper: None, max_paths: Default::default(), }); +pub static DEMO_BGP_CONFIG_UPDATE: LazyLock = + LazyLock::new(|| networking::BgpConfigCreate { + identity: IdentityMetadataCreateParams { + name: "as47".parse().unwrap(), + description: "BGP config for AS47".into(), + }, + bgp_announce_set_id: NameOrId::Name("instances".parse().unwrap()), + asn: 47, + vrf: None, + checker: None, + shaper: None, + max_paths: MaxPathConfig::new(2).unwrap(), + }); + pub const DEMO_BGP_ANNOUNCE_SET_URL: &'static str = "/v1/system/networking/bgp-announce-set"; pub static DEMO_BGP_ANNOUNCE: LazyLock = @@ -3439,6 +3454,9 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( serde_json::to_value(&*DEMO_BGP_CONFIG).unwrap(), ), AllowedMethod::Get, + AllowedMethod::Put( + serde_json::to_value(&*DEMO_BGP_CONFIG_UPDATE).unwrap(), + ), AllowedMethod::Delete, ], }, diff --git a/nexus/types/versions/src/bgp_configuration_update/mod.rs b/nexus/types/versions/src/bgp_configuration_update/mod.rs new file mode 100644 index 00000000000..c11cbd02dbe --- /dev/null +++ b/nexus/types/versions/src/bgp_configuration_update/mod.rs @@ -0,0 +1,5 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +pub mod networking; diff --git a/nexus/types/versions/src/bgp_configuration_update/networking.rs b/nexus/types/versions/src/bgp_configuration_update/networking.rs new file mode 100644 index 00000000000..7b129c06231 --- /dev/null +++ b/nexus/types/versions/src/bgp_configuration_update/networking.rs @@ -0,0 +1,37 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Networking types for the `BGP_CONFIGURATION_UPDATE` version. +//! +//! Changes in this version: +//! +//! * New [`BgpConfigUpdate`] type to allow updating a BGP configuration's +//! `name`, `description`, `max_paths` and `bgp_announce_set_id` fields +//! without deleting and recreating the object. + +use omicron_common::api::external::Name; +use omicron_common::api::external::NameOrId; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use sled_agent_types_versions::v20::early_networking::MaxPathConfig; + +/// Parameters for updating a BGP configuration. +/// +/// The `asn` field is intentionally not updatable; changing the autonomous +/// system number requires creating a new BGP configuration object, since many +/// things are keyed off the ASN. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpConfigUpdate { + /// Update the name of this BGP configuration. + pub name: Option, + + /// Update the description of this BGP configuration. + pub description: Option, + + /// Update the BGP announce set associated with this configuration. + pub bgp_announce_set_id: Option, + + /// Update the maximum number of equal-cost paths. + pub max_paths: Option, +} diff --git a/nexus/types/versions/src/latest.rs b/nexus/types/versions/src/latest.rs index c17e4d5b53e..92a1bac95c0 100644 --- a/nexus/types/versions/src/latest.rs +++ b/nexus/types/versions/src/latest.rs @@ -310,6 +310,8 @@ pub mod networking { pub use crate::v2026_05_07_00::networking::SwitchInterfaceConfig; pub use crate::v2026_05_07_00::networking::SwitchPortSettings; + + pub use crate::v2026_06_10_00::networking::BgpConfigUpdate; } pub mod oxql { diff --git a/nexus/types/versions/src/lib.rs b/nexus/types/versions/src/lib.rs index c0a3b4efe8a..0998a1a4d18 100644 --- a/nexus/types/versions/src/lib.rs +++ b/nexus/types/versions/src/lib.rs @@ -93,3 +93,5 @@ pub mod v2026_06_04_00; pub mod v2026_06_05_00; #[path = "instance_cpu_type_turin_v2/mod.rs"] pub mod v2026_06_08_00; +#[path = "bgp_configuration_update/mod.rs"] +pub mod v2026_06_10_00; diff --git a/openapi/nexus/nexus-2026060800.0.0-f1db6e.json.gitstub b/openapi/nexus/nexus-2026060800.0.0-f1db6e.json.gitstub new file mode 100644 index 00000000000..49ca319148e --- /dev/null +++ b/openapi/nexus/nexus-2026060800.0.0-f1db6e.json.gitstub @@ -0,0 +1 @@ +456b293ebc28e8419ce4829e90318a7ab500754e:openapi/nexus/nexus-2026060800.0.0-f1db6e.json diff --git a/openapi/nexus/nexus-2026060800.0.0-f1db6e.json b/openapi/nexus/nexus-2026061000.0.0-6e3e54.json similarity index 99% rename from openapi/nexus/nexus-2026060800.0.0-f1db6e.json rename to openapi/nexus/nexus-2026061000.0.0-6e3e54.json index c7680d95ce0..b7474d1338f 100644 --- a/openapi/nexus/nexus-2026060800.0.0-f1db6e.json +++ b/openapi/nexus/nexus-2026061000.0.0-6e3e54.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "2026060800.0.0" + "version": "2026061000.0.0" }, "paths": { "/device/auth": { @@ -10675,6 +10675,53 @@ "required": [] } }, + "put": { + "tags": [ + "system/networking" + ], + "summary": "Update BGP configuration", + "description": "Update the mutable fields of an existing BGP configuration. The `asn` field is intentionally not updatable; changing the autonomous system number requires creating a new BGP configuration object, since many things are keyed off the ASN.", + "operationId": "networking_bgp_config_update", + "parameters": [ + { + "in": "query", + "name": "name_or_id", + "description": "A name or id to use when selecting BGP config.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpConfigUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpConfig" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, "post": { "tags": [ "system/networking" @@ -17533,6 +17580,44 @@ "items" ] }, + "BgpConfigUpdate": { + "description": "Parameters for updating a BGP configuration.\n\nThe `asn` field is intentionally not updatable; changing the autonomous system number requires creating a new BGP configuration object, since many things are keyed off the ASN.", + "type": "object", + "properties": { + "bgp_announce_set_id": { + "nullable": true, + "description": "Update the BGP announce set associated with this configuration.", + "allOf": [ + { + "$ref": "#/components/schemas/NameOrId" + } + ] + }, + "description": { + "nullable": true, + "description": "Update the description of this BGP configuration.", + "type": "string" + }, + "max_paths": { + "nullable": true, + "description": "Update the maximum number of equal-cost paths.", + "allOf": [ + { + "$ref": "#/components/schemas/MaxPathConfig" + } + ] + }, + "name": { + "nullable": true, + "description": "Update the name of this BGP configuration.", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + } + } + }, "BgpExported": { "description": "Route exported to a peer.", "type": "object", diff --git a/openapi/nexus/nexus-latest.json b/openapi/nexus/nexus-latest.json index 3fa01e66b0b..bf4f3a14b6f 120000 --- a/openapi/nexus/nexus-latest.json +++ b/openapi/nexus/nexus-latest.json @@ -1 +1 @@ -nexus-2026060800.0.0-f1db6e.json \ No newline at end of file +nexus-2026061000.0.0-6e3e54.json \ No newline at end of file From 824b565030b5b59792c5a9f13f0ec28fa884a953 Mon Sep 17 00:00:00 2001 From: nicolassk Date: Wed, 10 Jun 2026 17:58:44 -0300 Subject: [PATCH 2/5] add smoke test for bgp_config_update --- nexus/db-queries/src/db/datastore/bgp.rs | 116 +++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/nexus/db-queries/src/db/datastore/bgp.rs b/nexus/db-queries/src/db/datastore/bgp.rs index ae2ae70f1f2..007f1c105e2 100644 --- a/nexus/db-queries/src/db/datastore/bgp.rs +++ b/nexus/db-queries/src/db/datastore/bgp.rs @@ -1185,9 +1185,125 @@ mod tests { use omicron_test_utils::dev; use oxnet::IpNet; use sled_agent_types::early_networking::ImportExportPolicy; + use sled_agent_types::early_networking::MaxPathConfig; use sled_agent_types::early_networking::RouterLifetimeConfig; use std::net::IpAddr; + #[tokio::test] + async fn test_update_bgp_config() { + let logctx = dev::test_setup_log("test_update_bgp_config"); + let db = TestDatabase::new_with_datastore(&logctx.log).await; + let (opctx, datastore) = (db.opctx(), db.datastore()); + + let config_name: Name = "config-name".parse().unwrap(); + let announce_name: Name = "announce-name".parse().unwrap(); + + let config = networking::BgpConfigCreate { + identity: IdentityMetadataCreateParams { + name: config_name.clone(), + description: String::from("a test config"), + }, + asn: 47, + bgp_announce_set_id: NameOrId::Name(announce_name.clone()), + vrf: None, + shaper: None, + checker: None, + max_paths: MaxPathConfig::new(1).unwrap(), + }; + + let new_announce_name: Name = "new-announce-name".parse().unwrap(); + let new_max_paths = MaxPathConfig::new(2).unwrap(); + let new_config_name: Name = "new-config-name".parse().unwrap(); + let new_description = String::from("updated description"); + + let update = networking::BgpConfigUpdate { + name: Some(new_config_name.clone()), + description: Some(new_description.clone()), + bgp_announce_set_id: Some(NameOrId::Name( + new_announce_name.clone(), + )), + max_paths: Some(new_max_paths), + }; + + // Make sure the announces exist + datastore + .bgp_create_announce_set( + &opctx, + &networking::BgpAnnounceSetCreate { + identity: IdentityMetadataCreateParams { + name: announce_name.clone(), + description: String::from("the first announce set"), + }, + announcement: Vec::default(), + }, + ) + .await + .expect("create bgp announce set"); + + let new_announce_id = datastore + .bgp_create_announce_set( + &opctx, + &networking::BgpAnnounceSetCreate { + identity: IdentityMetadataCreateParams { + name: new_announce_name.clone(), + description: String::from("the second announce set"), + }, + announcement: Vec::default(), + }, + ) + .await + .expect("create bgp announce set") + .0 + .identity + .id; + + // Try to update an inexistent BGP config + let res = datastore + .bgp_config_update( + &opctx, + &networking::BgpConfigSelector { + name_or_id: NameOrId::Name(config_name.clone()), + }, + &update, + ) + .await; + assert!(res.is_err()); + + // Create the BGP config + let config_id = datastore + .bgp_config_create(&opctx, &config) + .await + .expect("create bgp config") + .identity + .id; + + // Update the BGP config + datastore + .bgp_config_update( + &opctx, + &networking::BgpConfigSelector { + name_or_id: NameOrId::Name(config_name.clone()), + }, + &update, + ) + .await + .expect("update bgp config"); + + // Verify the BGP config was updated + let bgp_config = datastore + .bgp_config_get(&opctx, &NameOrId::Id(config_id)) + .await + .expect("get bgp config"); + + assert_eq!( + bgp_config.identity.name.to_string(), + new_config_name.to_string() + ); + assert_eq!(bgp_config.identity.description, new_description); + assert_eq!(bgp_config.bgp_announce_set_id, new_announce_id); + assert_eq!(bgp_config.max_paths.0, new_max_paths.as_u8()); + } + #[tokio::test] async fn test_delete_bgp_config_and_announce_set_by_name() { let logctx = dev::test_setup_log( From c3143bd630b2c07e31ef7eb1d0dd983d274b0595 Mon Sep 17 00:00:00 2001 From: nicolassk Date: Thu, 11 Jun 2026 15:10:28 -0300 Subject: [PATCH 3/5] properly cleanup the test --- nexus/db-queries/src/db/datastore/bgp.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nexus/db-queries/src/db/datastore/bgp.rs b/nexus/db-queries/src/db/datastore/bgp.rs index 007f1c105e2..6667edcc83a 100644 --- a/nexus/db-queries/src/db/datastore/bgp.rs +++ b/nexus/db-queries/src/db/datastore/bgp.rs @@ -1302,6 +1302,9 @@ mod tests { assert_eq!(bgp_config.identity.description, new_description); assert_eq!(bgp_config.bgp_announce_set_id, new_announce_id); assert_eq!(bgp_config.max_paths.0, new_max_paths.as_u8()); + + db.terminate().await; + logctx.cleanup_successful(); } #[tokio::test] From 16ae7a88f0cc4caa70a3f39189b44f426fbe0816 Mon Sep 17 00:00:00 2001 From: nicolassk Date: Thu, 11 Jun 2026 15:29:38 -0300 Subject: [PATCH 4/5] use IdentityMetadataUpdateParams --- nexus/db-queries/src/db/datastore/bgp.rs | 16 ++++++++++++---- .../src/bgp_configuration_update/networking.rs | 10 +++------- ...e54.json => nexus-2026061000.0.0-ef9fca.json} | 2 -- openapi/nexus/nexus-latest.json | 2 +- 4 files changed, 16 insertions(+), 14 deletions(-) rename openapi/nexus/{nexus-2026061000.0.0-6e3e54.json => nexus-2026061000.0.0-ef9fca.json} (99%) diff --git a/nexus/db-queries/src/db/datastore/bgp.rs b/nexus/db-queries/src/db/datastore/bgp.rs index 6667edcc83a..c714ccc8074 100644 --- a/nexus/db-queries/src/db/datastore/bgp.rs +++ b/nexus/db-queries/src/db/datastore/bgp.rs @@ -311,9 +311,14 @@ impl DataStore { } }; - let new_name = - update.name.as_ref().unwrap_or(existing.name()).to_string(); + let new_name = update + .identity + .name + .as_ref() + .unwrap_or(existing.name()) + .to_string(); let new_description = update + .identity .description .as_deref() .unwrap_or(existing.description()) @@ -1181,6 +1186,7 @@ mod tests { use nexus_db_model::SwitchPortBgpPeerConfig; use nexus_types::external_api::networking::BgpPeer; use omicron_common::api::external::IdentityMetadataCreateParams; + use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::Name; use omicron_test_utils::dev; use oxnet::IpNet; @@ -1217,8 +1223,10 @@ mod tests { let new_description = String::from("updated description"); let update = networking::BgpConfigUpdate { - name: Some(new_config_name.clone()), - description: Some(new_description.clone()), + identity: IdentityMetadataUpdateParams { + name: Some(new_config_name.clone()), + description: Some(new_description.clone()), + }, bgp_announce_set_id: Some(NameOrId::Name( new_announce_name.clone(), )), diff --git a/nexus/types/versions/src/bgp_configuration_update/networking.rs b/nexus/types/versions/src/bgp_configuration_update/networking.rs index 7b129c06231..374f9164a78 100644 --- a/nexus/types/versions/src/bgp_configuration_update/networking.rs +++ b/nexus/types/versions/src/bgp_configuration_update/networking.rs @@ -10,8 +10,7 @@ //! `name`, `description`, `max_paths` and `bgp_announce_set_id` fields //! without deleting and recreating the object. -use omicron_common::api::external::Name; -use omicron_common::api::external::NameOrId; +use omicron_common::api::external::{IdentityMetadataUpdateParams, NameOrId}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use sled_agent_types_versions::v20::early_networking::MaxPathConfig; @@ -23,11 +22,8 @@ use sled_agent_types_versions::v20::early_networking::MaxPathConfig; /// things are keyed off the ASN. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct BgpConfigUpdate { - /// Update the name of this BGP configuration. - pub name: Option, - - /// Update the description of this BGP configuration. - pub description: Option, + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, /// Update the BGP announce set associated with this configuration. pub bgp_announce_set_id: Option, diff --git a/openapi/nexus/nexus-2026061000.0.0-6e3e54.json b/openapi/nexus/nexus-2026061000.0.0-ef9fca.json similarity index 99% rename from openapi/nexus/nexus-2026061000.0.0-6e3e54.json rename to openapi/nexus/nexus-2026061000.0.0-ef9fca.json index b7474d1338f..843c1c70c03 100644 --- a/openapi/nexus/nexus-2026061000.0.0-6e3e54.json +++ b/openapi/nexus/nexus-2026061000.0.0-ef9fca.json @@ -17595,7 +17595,6 @@ }, "description": { "nullable": true, - "description": "Update the description of this BGP configuration.", "type": "string" }, "max_paths": { @@ -17609,7 +17608,6 @@ }, "name": { "nullable": true, - "description": "Update the name of this BGP configuration.", "allOf": [ { "$ref": "#/components/schemas/Name" diff --git a/openapi/nexus/nexus-latest.json b/openapi/nexus/nexus-latest.json index bf4f3a14b6f..166b3c9c097 120000 --- a/openapi/nexus/nexus-latest.json +++ b/openapi/nexus/nexus-latest.json @@ -1 +1 @@ -nexus-2026061000.0.0-6e3e54.json \ No newline at end of file +nexus-2026061000.0.0-ef9fca.json \ No newline at end of file From dd68cb56b7e31b8526e8a2cac2a25cc0f0af66e2 Mon Sep 17 00:00:00 2001 From: nicolassk Date: Thu, 11 Jun 2026 17:17:19 -0300 Subject: [PATCH 5/5] fix to use BgpConfigUpdate on VERIFY_ENDPOINTS integration test --- nexus/tests/integration_tests/endpoints.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 2e9bc5f5972..7524380a505 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -1015,18 +1015,14 @@ pub static DEMO_BGP_CONFIG: LazyLock = shaper: None, max_paths: Default::default(), }); -pub static DEMO_BGP_CONFIG_UPDATE: LazyLock = - LazyLock::new(|| networking::BgpConfigCreate { - identity: IdentityMetadataCreateParams { - name: "as47".parse().unwrap(), - description: "BGP config for AS47".into(), +pub static DEMO_BGP_CONFIG_UPDATE: LazyLock = + LazyLock::new(|| networking::BgpConfigUpdate { + identity: IdentityMetadataUpdateParams { + name: Some("as47".parse().unwrap()), + description: Some("BGP config for AS47".into()), }, - bgp_announce_set_id: NameOrId::Name("instances".parse().unwrap()), - asn: 47, - vrf: None, - checker: None, - shaper: None, - max_paths: MaxPathConfig::new(2).unwrap(), + bgp_announce_set_id: Some(NameOrId::Name("instances".parse().unwrap())), + max_paths: Some(MaxPathConfig::new(1).unwrap()), }); pub const DEMO_BGP_ANNOUNCE_SET_URL: &'static str =