From cd6c856de11a308fc3c08d6bba86455d64f7b27f Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Tue, 18 Jun 2024 13:15:27 -0500 Subject: [PATCH 01/37] WIP: refactor of operator api networking endpoints WIP: refactor address lot apis --- common/src/api/external/mod.rs | 3 - .../src/db/datastore/address_lot.rs | 133 +++++++++--------- nexus/db-queries/src/db/datastore/mod.rs | 1 - nexus/src/app/address_lot.rs | 53 ++++--- nexus/src/app/rack.rs | 19 +-- nexus/src/external_api/http_entrypoints.rs | 85 ++++++++++- nexus/tests/integration_tests/address_lots.rs | 122 ++++++++++------ nexus/tests/integration_tests/endpoints.rs | 22 ++- nexus/tests/integration_tests/switch_port.rs | 36 +++-- nexus/tests/output/nexus_tags.txt | 2 + .../output/uncovered-authz-endpoints.txt | 1 + nexus/types/src/external_api/params.rs | 2 - openapi/nexus.json | 109 +++++++++++--- .../tests/output/self-stat-schema.json | 4 +- 14 files changed, 412 insertions(+), 180 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 0af437bd993..e8c9d7273b3 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2034,9 +2034,6 @@ impl std::fmt::Display for Digest { pub struct AddressLotCreateResponse { /// The address lot that was created. pub lot: AddressLot, - - /// The address lot blocks that were created. - pub blocks: Vec, } /// Represents an address lot object, containing the id of the lot that can be diff --git a/nexus/db-queries/src/db/datastore/address_lot.rs b/nexus/db-queries/src/db/datastore/address_lot.rs index 459c2a4c361..2470a58ef6f 100644 --- a/nexus/db-queries/src/db/datastore/address_lot.rs +++ b/nexus/db-queries/src/db/datastore/address_lot.rs @@ -16,6 +16,8 @@ use crate::db::pagination::paginated; use crate::transaction_retry::OptionalError; use async_bb8_diesel::{AsyncRunQueryDsl, Connection}; use chrono::Utc; +use diesel::pg::sql_types; +use diesel::IntoSql; use diesel::{ExpressionMethods, QueryDsl, SelectableHelper}; use diesel_dtrace::DTraceConnection; use ipnetwork::IpNetwork; @@ -27,23 +29,15 @@ use omicron_common::api::external::{ LookupResult, ResourceType, }; use ref_cast::RefCast; -use serde::{Deserialize, Serialize}; use uuid::Uuid; -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct AddressLotCreateResult { - pub lot: AddressLot, - pub blocks: Vec, -} - impl DataStore { pub async fn address_lot_create( &self, opctx: &OpContext, params: ¶ms::AddressLotCreate, - ) -> CreateResult { + ) -> CreateResult { use db::schema::address_lot::dsl as lot_dsl; - use db::schema::address_lot_block::dsl as block_dsl; let conn = self.pool_connection_authorized(opctx).await?; @@ -81,63 +75,7 @@ impl DataStore { } }; - let desired_blocks: Vec = params - .blocks - .iter() - .map(|b| { - AddressLotBlock::new( - db_lot.id(), - b.first_address.into(), - b.last_address.into(), - ) - }) - .collect(); - - let found_blocks: Vec = - block_dsl::address_lot_block - .filter(block_dsl::address_lot_id.eq(db_lot.id())) - .filter( - block_dsl::first_address.eq_any( - desired_blocks - .iter() - .map(|b| b.first_address) - .collect::>(), - ), - ) - .filter( - block_dsl::last_address.eq_any( - desired_blocks - .iter() - .map(|b| b.last_address) - .collect::>(), - ), - ) - .get_results_async(&conn) - .await?; - - let mut blocks = vec![]; - - // If the block is found in the database, use the found block. - // If the block is not found in the database, insert it. - for desired_block in desired_blocks { - let block = match found_blocks.iter().find(|db_b| { - db_b.first_address == desired_block.first_address - && db_b.last_address == desired_block.last_address - }) { - Some(block) => block.clone(), - None => { - diesel::insert_into(block_dsl::address_lot_block) - .values(desired_block) - .returning(AddressLotBlock::as_returning()) - .get_results_async(&conn) - .await?[0] - .clone() - } - }; - blocks.push(block); - } - - Ok(AddressLotCreateResult { lot: db_lot, blocks }) + Ok(db_lot) }) .await .map_err(|e| { @@ -263,6 +201,69 @@ impl DataStore { .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) } + pub async fn address_lot_block_create( + &self, + opctx: &OpContext, + address_lot_id: Uuid, + params: params::AddressLotBlockCreate, + ) -> CreateResult { + use db::schema::address_lot_block::dsl; + + let conn = self.pool_connection_authorized(opctx).await?; + + self.transaction_retry_wrapper("address_lot_create") + .transaction(&conn, |conn| async move { + let found_block: Option = + dsl::address_lot_block + .filter(dsl::address_lot_id.eq(address_lot_id)) + .filter( + dsl::first_address + .eq(IpNetwork::from(params.first_address)), + ) + .filter( + dsl::last_address + .eq(IpNetwork::from(params.last_address)), + ) + .select(AddressLotBlock::as_select()) + .limit(1) + .first_async(&conn) + .await + .ok(); + + let new_block = AddressLotBlock::new( + address_lot_id, + IpNetwork::from(params.first_address), + IpNetwork::from(params.last_address), + ); + + let db_block = match found_block { + Some(v) => v, + None => { + diesel::insert_into(dsl::address_lot_block) + .values(new_block) + .returning(AddressLotBlock::as_returning()) + .get_result_async(&conn) + .await? + } + }; + + Ok(db_block) + }) + .await + .map_err(|e| { + public_error_from_diesel( + e, + ErrorHandler::Conflict( + ResourceType::AddressLotBlock, + &format!( + "block covering range {} - {}", + params.first_address, params.last_address + ), + ), + ) + }) + } + pub async fn address_lot_id_for_block_id( &self, opctx: &OpContext, diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index 0a48f33ebdf..70ac89f593b 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -106,7 +106,6 @@ mod volume; mod vpc; mod zpool; -pub use address_lot::AddressLotCreateResult; pub use dns::DataStoreDnsTest; pub use dns::DnsVersionUpdateBuilder; pub use instance::InstanceAndActiveVmm; diff --git a/nexus/src/app/address_lot.rs b/nexus/src/app/address_lot.rs index 847021bdd42..fe65f536112 100644 --- a/nexus/src/app/address_lot.rs +++ b/nexus/src/app/address_lot.rs @@ -7,7 +7,6 @@ use db::model::{AddressLot, AddressLotBlock}; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; -use nexus_db_queries::db::datastore::AddressLotCreateResult; use nexus_db_queries::db::lookup; use nexus_db_queries::db::lookup::LookupPath; use omicron_common::api::external::http_pagination::PaginatedBy; @@ -45,9 +44,8 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, params: params::AddressLotCreate, - ) -> CreateResult { + ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; - validate_blocks(¶ms)?; self.db_datastore.address_lot_create(opctx, ¶ms).await } @@ -70,6 +68,29 @@ impl super::Nexus { self.db_datastore.address_lot_list(opctx, pagparams).await } + pub(crate) async fn address_lot_block_create( + self: &Arc, + opctx: &OpContext, + address_lot_id: Uuid, + block: params::AddressLotBlockCreate, + ) -> CreateResult { + opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; + validate_block(&block)?; + self.db_datastore + .address_lot_block_create(opctx, address_lot_id, block) + .await + } + + pub(crate) async fn address_lot_block_delete( + self: &Arc, + opctx: &OpContext, + address_lot: &lookup::AddressLot<'_>, + block: params::AddressLotBlockCreate, + ) -> DeleteResult { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + todo!("delete address lot block") + } + pub(crate) async fn address_lot_block_list( self: &Arc, opctx: &OpContext, @@ -84,20 +105,18 @@ impl super::Nexus { } } -fn validate_blocks(lot: ¶ms::AddressLotCreate) -> Result<(), Error> { - for b in &lot.blocks { - match (&b.first_address, &b.last_address) { - (IpAddr::V4(first), IpAddr::V4(last)) => { - validate_v4_block(first, last)? - } - (IpAddr::V6(first), IpAddr::V6(last)) => { - validate_v6_block(first, last)? - } - _ => { - return Err(Error::invalid_request( - "Block bounds must be in same address family", - )); - } +fn validate_block(block: ¶ms::AddressLotBlockCreate) -> Result<(), Error> { + match (&block.first_address, &block.last_address) { + (IpAddr::V4(first), IpAddr::V4(last)) => { + validate_v4_block(first, last)? + } + (IpAddr::V6(first), IpAddr::V6(last)) => { + validate_v6_block(first, last)? + } + _ => { + return Err(Error::invalid_request( + "Block bounds must be in same address family", + )); } } Ok(()) diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index 780cb85f3f8..13fb7d7ef1a 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -382,7 +382,7 @@ impl super::Nexus { let blocks = vec![ipv4_block]; - let address_lot_params = AddressLotCreate { identity, kind, blocks }; + let address_lot_params = AddressLotCreate { identity, kind }; match self .db_datastore @@ -425,14 +425,15 @@ impl super::Nexus { ), }, kind: AddressLotKind::Infra, - blocks: bgp_config - .originate - .iter() - .map(|o| AddressLotBlockCreate { - first_address: o.first_addr().into(), - last_address: o.last_addr().into(), - }) - .collect(), + // TODO: Levon - Move to new creation logic + // blocks: bgp_config + // .originate + // .iter() + // .map(|o| AddressLotBlockCreate { + // first_address: o.first_addr().into(), + // last_address: o.last_addr().into(), + // }) + // .collect(), }, ) .await diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 350836441e1..7abaaa2db34 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -257,7 +257,11 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register(networking_address_lot_list)?; api.register(networking_address_lot_create)?; api.register(networking_address_lot_delete)?; + + // TODO: Levon - Operator-Accessible Address Lot Block API api.register(networking_address_lot_block_list)?; + api.register(networking_address_lot_block_add)?; + api.register(networking_address_lot_block_remove)?; api.register(networking_loopback_address_create)?; api.register(networking_loopback_address_delete)?; @@ -3417,11 +3421,9 @@ async fn networking_address_lot_create( let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let result = nexus.address_lot_create(&opctx, params).await?; - let lot: AddressLot = result.lot.into(); - let blocks: Vec = - result.blocks.iter().map(|b| b.clone().into()).collect(); + let lot: AddressLot = result.into(); - Ok(HttpResponseCreated(AddressLotCreateResponse { lot, blocks })) + Ok(HttpResponseCreated(AddressLotCreateResponse { lot })) }; apictx .context @@ -3495,6 +3497,81 @@ async fn networking_address_lot_list( .await } +/// Add block to address lot +#[endpoint { + method = POST, + path = "/v1/system/networking/address-lot/{address_lot}/blocks", + tags = ["system/networking"], +}] +async fn networking_address_lot_block_add( + rqctx: RequestContext, + path_params: Path, + block: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + let address_lot_lookup = + nexus.address_lot_lookup(&opctx, path.address_lot)?; + + let (.., authz_address_lot) = + address_lot_lookup.lookup_for(authz::Action::CreateChild).await?; + + let result = nexus + .address_lot_block_create( + &opctx, + authz_address_lot.id(), + block.into_inner(), + ) + .await?; + + Ok(HttpResponseCreated(result.into())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct AddressLotBlockPath { + /// The address lot the block belongs to + address_lot: NameOrId, + + /// The block to delete from the address lot + block: NameOrId, +} + +/// Remove block from address lot +#[endpoint { + method = DELETE, + path = "/v1/system/networking/address-lot/{address_lot}/blocks/{block}", + tags = ["system/networking"], +}] +async fn networking_address_lot_block_remove( + rqctx: RequestContext, + path_params: Path, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + let address_lot_lookup = + nexus.address_lot_lookup(&opctx, path.address_lot)?; + + todo!("implement address lot block remove logic") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + /// List blocks in address lot #[endpoint { method = GET, diff --git a/nexus/tests/integration_tests/address_lots.rs b/nexus/tests/integration_tests/address_lots.rs index 7860dd463c0..fe25daf72ee 100644 --- a/nexus/tests/integration_tests/address_lots.rs +++ b/nexus/tests/integration_tests/address_lots.rs @@ -45,10 +45,6 @@ async fn test_address_lot_basic_crud(ctx: &ControlPlaneTestContext) { description: "an address parking lot".into(), }, kind: AddressLotKind::Infra, - blocks: vec![AddressLotBlockCreate { - first_address: "203.0.113.10".parse().unwrap(), - last_address: "203.0.113.20".parse().unwrap(), - }], }; let response: AddressLotCreateResponse = NexusRequest::objects_post( @@ -64,11 +60,36 @@ async fn test_address_lot_basic_crud(ctx: &ControlPlaneTestContext) { .unwrap(); let address_lot = response.lot; - let blocks = response.blocks; + + let block_params = AddressLotBlockCreate { + first_address: "203.0.113.10".parse().unwrap(), + last_address: "203.0.113.20".parse().unwrap(), + }; + + NexusRequest::objects_post( + client, + "/v1/system/networking/address-lot/parkinglot/blocks", + &block_params, + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap(); + + // Verify there are lot blocks + let blocks = NexusRequest::iter_collection_authn::( + client, + "/v1/system/networking/address-lot/parkinglot/blocks", + "", + None, + ) + .await + .expect("Failed to list address lot blocks") + .all_items; assert_eq!(address_lot.identity.name, params.identity.name); assert_eq!(address_lot.identity.description, params.identity.description); - assert_eq!(blocks.len(), params.blocks.len()); + assert_eq!(blocks.len(), 1, "Expected 1 address lot block"); assert_eq!( blocks[0].first_address, "203.0.113.10".parse::().unwrap() @@ -91,20 +112,6 @@ async fn test_address_lot_basic_crud(ctx: &ControlPlaneTestContext) { assert_eq!(lots.len(), 2, "Expected 2 lots"); assert_eq!(lots[1], address_lot); - - // Verify there are lot blocks - let blist = NexusRequest::iter_collection_authn::( - client, - "/v1/system/networking/address-lot/parkinglot/blocks", - "", - None, - ) - .await - .expect("Failed to list address lot blocks") - .all_items; - - assert_eq!(blist.len(), 1, "Expected 1 address lot block"); - assert_eq!(blist[0], blocks[0]); } #[nexus_test] @@ -114,52 +121,75 @@ async fn test_address_lot_invalid_range(ctx: &ControlPlaneTestContext) { let mut params = Vec::new(); // Try to create a lot with different address families - params.push(AddressLotCreate { - identity: IdentityMetadataCreateParams { - name: "family".parse().unwrap(), - description: "an address parking lot".into(), + params.push(( + AddressLotCreate { + identity: IdentityMetadataCreateParams { + name: "family".parse().unwrap(), + description: "an address parking lot".into(), + }, + kind: AddressLotKind::Infra, }, - kind: AddressLotKind::Infra, - blocks: vec![AddressLotBlockCreate { + AddressLotBlockCreate { first_address: "203.0.113.10".parse().unwrap(), last_address: "fd00:1701::d".parse().unwrap(), - }], - }); + }, + )); // Try to create an IPv4 lot where the first address comes after the second. - params.push(AddressLotCreate { - identity: IdentityMetadataCreateParams { - name: "v4".parse().unwrap(), - description: "an address parking lot".into(), + params.push(( + AddressLotCreate { + identity: IdentityMetadataCreateParams { + name: "v4".parse().unwrap(), + description: "an address parking lot".into(), + }, + kind: AddressLotKind::Infra, }, - kind: AddressLotKind::Infra, - blocks: vec![AddressLotBlockCreate { + AddressLotBlockCreate { first_address: "203.0.113.20".parse().unwrap(), last_address: "203.0.113.10".parse().unwrap(), - }], - }); + }, + )); // Try to create an IPv6 lot where the first address comes after the second. - params.push(AddressLotCreate { - identity: IdentityMetadataCreateParams { - name: "v6".parse().unwrap(), - description: "an address parking lot".into(), + params.push(( + AddressLotCreate { + identity: IdentityMetadataCreateParams { + name: "v6".parse().unwrap(), + description: "an address parking lot".into(), + }, + kind: AddressLotKind::Infra, }, - kind: AddressLotKind::Infra, - blocks: vec![AddressLotBlockCreate { + AddressLotBlockCreate { first_address: "fd00:1701::d".parse().unwrap(), last_address: "fd00:1701::a".parse().unwrap(), - }], - }); + }, + )); - for params in ¶ms { + for (lot_params, block_params) in ¶ms { NexusRequest::new( RequestBuilder::new( client, Method::POST, "/v1/system/networking/address-lot", ) - .body(Some(¶ms)) + .body(Some(&lot_params)) + .expect_status(Some(StatusCode::CREATED)), + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap(); + + NexusRequest::new( + RequestBuilder::new( + client, + Method::POST, + &format!( + "/v1/system/networking/address-lot/{}/blocks", + lot_params.identity.name + ), + ) + .body(Some(&block_params)) .expect_status(Some(StatusCode::BAD_REQUEST)), ) .authn_as(AuthnMode::PrivilegedUser) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index ca46a8bf068..692c38a446b 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -553,10 +553,12 @@ pub static DEMO_ADDRESS_LOT_CREATE: Lazy = description: "an address parking lot".into(), }, kind: AddressLotKind::Infra, - blocks: vec![params::AddressLotBlockCreate { - first_address: "203.0.113.10".parse().unwrap(), - last_address: "203.0.113.20".parse().unwrap(), - }], + }); + +pub static DEMO_ADDRESS_LOT_BLOCK_CREATE: Lazy = + Lazy::new(|| params::AddressLotBlockCreate { + first_address: "203.0.113.10".parse().unwrap(), + last_address: "203.0.113.20".parse().unwrap(), }); pub const DEMO_BGP_CONFIG_CREATE_URL: &'static str = @@ -2223,6 +2225,18 @@ pub static VERIFY_ENDPOINTS: Lazy> = Lazy::new(|| { ], }, + VerifyEndpoint { + url: &DEMO_ADDRESS_LOT_BLOCKS_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value(&*DEMO_ADDRESS_LOT_BLOCK_CREATE).unwrap(), + ), + AllowedMethod::Get + ], + }, + VerifyEndpoint { url: &DEMO_ADDRESS_LOT_URL, visibility: Visibility::Protected, diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index 0b71ddb2cfe..b49d7d43990 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -40,18 +40,19 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { description: "an address parking lot".into(), }, kind: AddressLotKind::Infra, - blocks: vec![ - AddressLotBlockCreate { - first_address: "203.0.113.10".parse().unwrap(), - last_address: "203.0.113.20".parse().unwrap(), - }, - AddressLotBlockCreate { - first_address: "1.2.3.0".parse().unwrap(), - last_address: "1.2.3.255".parse().unwrap(), - }, - ], }; + let block_params = vec![ + AddressLotBlockCreate { + first_address: "203.0.113.10".parse().unwrap(), + last_address: "203.0.113.20".parse().unwrap(), + }, + AddressLotBlockCreate { + first_address: "1.2.3.0".parse().unwrap(), + last_address: "1.2.3.255".parse().unwrap(), + }, + ]; + NexusRequest::objects_post( client, "/v1/system/networking/address-lot", @@ -62,6 +63,21 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { .await .unwrap(); + for params in block_params { + NexusRequest::objects_post( + client, + &format!( + "/v1/system/networking/address-lot/{}/blocks", + lot_params.identity.name + ), + ¶ms, + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap(); + } + // Create BGP announce set let announce_set = BgpAnnounceSetCreate { identity: IdentityMetadataCreateParams { diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index a32fe5c4b9f..482b250341e 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -170,7 +170,9 @@ ip_pool_silo_update PUT /v1/system/ip-pools/{pool}/sil ip_pool_update PUT /v1/system/ip-pools/{pool} ip_pool_utilization_view GET /v1/system/ip-pools/{pool}/utilization ip_pool_view GET /v1/system/ip-pools/{pool} +networking_address_lot_block_add POST /v1/system/networking/address-lot/{address_lot}/blocks networking_address_lot_block_list GET /v1/system/networking/address-lot/{address_lot}/blocks +networking_address_lot_block_remove DELETE /v1/system/networking/address-lot/{address_lot}/blocks/{block} networking_address_lot_create POST /v1/system/networking/address-lot networking_address_lot_delete DELETE /v1/system/networking/address-lot/{address_lot} networking_address_lot_list GET /v1/system/networking/address-lot diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index c5091c5a3bc..39e5b76448d 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -1,5 +1,6 @@ API endpoints with no coverage in authz tests: probe_delete (delete "/experimental/v1/probes/{probe}") +networking_address_lot_block_remove (delete "/v1/system/networking/address-lot/{address_lot}/blocks/{block}") probe_list (get "/experimental/v1/probes") probe_view (get "/experimental/v1/probes/{probe}") ping (get "/v1/ping") diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index ac169a35ee7..7b68f711bec 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1408,8 +1408,6 @@ pub struct AddressLotCreate { pub identity: IdentityMetadataCreateParams, /// The kind of address lot to create. pub kind: AddressLotKind, - /// The blocks to add along with the new address lot. - pub blocks: Vec, } /// Parameters for creating an address lot block. Fist and last addresses are diff --git a/openapi/nexus.json b/openapi/nexus.json index 01ec9aeb569..6263954250d 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -6303,6 +6303,99 @@ "x-dropshot-pagination": { "required": [] } + }, + "post": { + "tags": [ + "system/networking" + ], + "summary": "Add block to address lot", + "operationId": "networking_address_lot_block_add", + "parameters": [ + { + "in": "path", + "name": "address_lot", + "description": "Name or ID of the address lot", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressLotBlockCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressLotBlock" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/address-lot/{address_lot}/blocks/{block}": { + "delete": { + "tags": [ + "system/networking" + ], + "summary": "Remove block from address lot", + "operationId": "networking_address_lot_block_remove", + "parameters": [ + { + "in": "path", + "name": "address_lot", + "description": "The address lot the block belongs to", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "block", + "description": "The block to delete from the address lot", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressLotBlockResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } } }, "/v1/system/networking/allow-list": { @@ -9146,13 +9239,6 @@ "description": "Parameters for creating an address lot.", "type": "object", "properties": { - "blocks": { - "description": "The blocks to add along with the new address lot.", - "type": "array", - "items": { - "$ref": "#/components/schemas/AddressLotBlockCreate" - } - }, "description": { "type": "string" }, @@ -9169,7 +9255,6 @@ } }, "required": [ - "blocks", "description", "kind", "name" @@ -9179,13 +9264,6 @@ "description": "An address lot and associated blocks resulting from creating an address lot.", "type": "object", "properties": { - "blocks": { - "description": "The address lot blocks that were created.", - "type": "array", - "items": { - "$ref": "#/components/schemas/AddressLotBlock" - } - }, "lot": { "description": "The address lot that was created.", "allOf": [ @@ -9196,7 +9274,6 @@ } }, "required": [ - "blocks", "lot" ] }, diff --git a/oximeter/collector/tests/output/self-stat-schema.json b/oximeter/collector/tests/output/self-stat-schema.json index 019e05b4946..10949446ceb 100644 --- a/oximeter/collector/tests/output/self-stat-schema.json +++ b/oximeter/collector/tests/output/self-stat-schema.json @@ -39,7 +39,7 @@ } ], "datum_type": "cumulative_u64", - "created": "2024-06-04T20:49:05.675711686Z" + "created": "2024-06-18T17:25:52.228347655Z" }, "oximeter_collector:failed_collections": { "timeseries_name": "oximeter_collector:failed_collections", @@ -86,6 +86,6 @@ } ], "datum_type": "cumulative_u64", - "created": "2024-06-04T20:49:05.676050088Z" + "created": "2024-06-18T17:25:52.228957350Z" } } \ No newline at end of file From 65b25dc5b6434ede2dc89766e621adbf982ed64d Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 4 Jul 2024 19:06:04 +0000 Subject: [PATCH 02/37] fix address lot testing --- nexus/tests/integration_tests/endpoints.rs | 11 +---------- nexus/tests/integration_tests/unauthorized.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 11a60a9aa4d..85888975d68 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -2232,7 +2232,7 @@ pub static VERIFY_ENDPOINTS: Lazy> = Lazy::new(|| { VerifyEndpoint { url: &DEMO_ADDRESS_LOT_BLOCKS_URL, - visibility: Visibility::Public, + visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![ AllowedMethod::Post( @@ -2251,15 +2251,6 @@ pub static VERIFY_ENDPOINTS: Lazy> = Lazy::new(|| { ] }, - VerifyEndpoint { - url: &DEMO_ADDRESS_LOT_BLOCKS_URL, - visibility: Visibility::Protected, - unprivileged_access: UnprivilegedAccess::None, - allowed_methods: vec![ - AllowedMethod::GetNonexistent - ], - }, - VerifyEndpoint { url: &DEMO_LOOPBACK_CREATE_URL, visibility: Visibility::Public, diff --git a/nexus/tests/integration_tests/unauthorized.rs b/nexus/tests/integration_tests/unauthorized.rs index 93e40dbc2ee..1d7018e271a 100644 --- a/nexus/tests/integration_tests/unauthorized.rs +++ b/nexus/tests/integration_tests/unauthorized.rs @@ -214,6 +214,19 @@ static SETUP_REQUESTS: Lazy> = Lazy::new(|| { &*DEMO_SILO_USER_ID_SET_PASSWORD_URL, ], }, + // Create the default Address Lot + SetupReq::Post { + url: &DEMO_ADDRESS_LOTS_URL, + body: serde_json::to_value(&*DEMO_ADDRESS_LOT_CREATE).unwrap(), + id_routes: vec!["/v1/system/networking/address-lot/{id}"], + }, + // Create the default Address Lot Block + SetupReq::Post { + url: &DEMO_ADDRESS_LOT_BLOCKS_URL, + body: serde_json::to_value(&*DEMO_ADDRESS_LOT_BLOCK_CREATE) + .unwrap(), + id_routes: vec![], + }, // Create the default IP pool SetupReq::Post { url: &DEMO_IP_POOLS_URL, From 7bccb1bd26096a34b10e0bf3a1566cacc3a2e18a Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Mon, 8 Jul 2024 20:58:12 +0000 Subject: [PATCH 03/37] refactor address lot APIs to resemble ip pools --- .../src/db/datastore/address_lot.rs | 82 +++++++++++++++- nexus/src/app/address_lot.rs | 15 +-- nexus/src/app/rack.rs | 86 +++++++++++++---- nexus/src/external_api/http_entrypoints.rs | 36 +++---- nexus/tests/integration_tests/address_lots.rs | 93 +++++++++++-------- nexus/tests/integration_tests/endpoints.rs | 38 +++++++- nexus/tests/integration_tests/switch_port.rs | 6 +- nexus/tests/integration_tests/unauthorized.rs | 2 +- nexus/tests/output/nexus_tags.txt | 4 +- .../output/uncovered-authz-endpoints.txt | 1 - .../output/unexpected-authz-endpoints.txt | 1 + nexus/types/src/external_api/params.rs | 2 +- openapi/nexus.json | 42 ++++----- 13 files changed, 286 insertions(+), 122 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/address_lot.rs b/nexus/db-queries/src/db/datastore/address_lot.rs index 2470a58ef6f..38fb90e58c7 100644 --- a/nexus/db-queries/src/db/datastore/address_lot.rs +++ b/nexus/db-queries/src/db/datastore/address_lot.rs @@ -16,13 +16,10 @@ use crate::db::pagination::paginated; use crate::transaction_retry::OptionalError; use async_bb8_diesel::{AsyncRunQueryDsl, Connection}; use chrono::Utc; -use diesel::pg::sql_types; -use diesel::IntoSql; use diesel::{ExpressionMethods, QueryDsl, SelectableHelper}; use diesel_dtrace::DTraceConnection; use ipnetwork::IpNetwork; use nexus_types::external_api::params; -use nexus_types::identity::Resource; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ CreateResult, DataPageParams, DeleteResult, Error, ListResultVec, @@ -205,7 +202,7 @@ impl DataStore { &self, opctx: &OpContext, address_lot_id: Uuid, - params: params::AddressLotBlockCreate, + params: params::AddressLotBlock, ) -> CreateResult { use db::schema::address_lot_block::dsl; @@ -264,6 +261,83 @@ impl DataStore { }) } + pub async fn address_lot_block_delete( + &self, + opctx: &OpContext, + address_lot_id: Uuid, + params: params::AddressLotBlock, + ) -> DeleteResult { + use db::schema::address_lot_block::dsl; + use db::schema::address_lot_rsvd_block::dsl as rsvd_block_dsl; + + #[derive(Debug)] + enum AddressLotBlockDeleteError { + BlockInUse, + } + + let conn = self.pool_connection_authorized(opctx).await?; + + let err = OptionalError::new(); + + self.transaction_retry_wrapper("address_lot_delete") + .transaction(&conn, |conn| { + let err = err.clone(); + async move { + let rsvd: Vec = + rsvd_block_dsl::address_lot_rsvd_block + .filter( + rsvd_block_dsl::address_lot_id + .eq(address_lot_id), + ) + .filter( + rsvd_block_dsl::first_address + .eq(IpNetwork::from(params.first_address)), + ) + .filter( + rsvd_block_dsl::last_address + .eq(IpNetwork::from(params.last_address)), + ) + .select(AddressLotReservedBlock::as_select()) + .limit(1) + .load_async(&conn) + .await?; + + if !rsvd.is_empty() { + return Err( + err.bail(AddressLotBlockDeleteError::BlockInUse) + ); + } + + diesel::delete(dsl::address_lot_block) + .filter(dsl::address_lot_id.eq(address_lot_id)) + .filter( + dsl::first_address + .eq(IpNetwork::from(params.first_address)), + ) + .filter( + dsl::last_address + .eq(IpNetwork::from(params.last_address)), + ) + .execute_async(&conn) + .await?; + + Ok(()) + } + }) + .await + .map_err(|e| { + if let Some(err) = err.take() { + match err { + AddressLotBlockDeleteError::BlockInUse => { + Error::invalid_request("block is in use") + } + } + } else { + public_error_from_diesel(e, ErrorHandler::Server) + } + }) + } + pub async fn address_lot_id_for_block_id( &self, opctx: &OpContext, diff --git a/nexus/src/app/address_lot.rs b/nexus/src/app/address_lot.rs index fe65f536112..8289dab14c4 100644 --- a/nexus/src/app/address_lot.rs +++ b/nexus/src/app/address_lot.rs @@ -72,7 +72,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, address_lot_id: Uuid, - block: params::AddressLotBlockCreate, + block: params::AddressLotBlock, ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; validate_block(&block)?; @@ -84,11 +84,14 @@ impl super::Nexus { pub(crate) async fn address_lot_block_delete( self: &Arc, opctx: &OpContext, - address_lot: &lookup::AddressLot<'_>, - block: params::AddressLotBlockCreate, + address_lot_id: Uuid, + block: params::AddressLotBlock, ) -> DeleteResult { - opctx.authorize(authz::Action::Read, &authz::FLEET).await?; - todo!("delete address lot block") + opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; + validate_block(&block)?; + self.db_datastore + .address_lot_block_delete(opctx, address_lot_id, block) + .await } pub(crate) async fn address_lot_block_list( @@ -105,7 +108,7 @@ impl super::Nexus { } } -fn validate_block(block: ¶ms::AddressLotBlockCreate) -> Result<(), Error> { +fn validate_block(block: ¶ms::AddressLotBlock) -> Result<(), Error> { match (&block.first_address, &block.last_address) { (IpAddr::V4(first), IpAddr::V4(last)) => { validate_v4_block(first, last)? diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index bf480893aed..be5d247597f 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -28,7 +28,7 @@ use nexus_types::deployment::CockroachDbClusterVersion; use nexus_types::deployment::SledFilter; use nexus_types::external_api::params::Address; use nexus_types::external_api::params::AddressConfig; -use nexus_types::external_api::params::AddressLotBlockCreate; +use nexus_types::external_api::params::AddressLotBlock; use nexus_types::external_api::params::BgpAnnounceSetCreate; use nexus_types::external_api::params::BgpAnnouncementCreate; use nexus_types::external_api::params::BgpConfigCreate; @@ -47,6 +47,7 @@ use nexus_types::external_api::shared::SiloIdentityMode; use nexus_types::external_api::shared::SiloRole; use nexus_types::external_api::shared::UninitializedSled; use nexus_types::external_api::views; +use nexus_types::identity::Resource; use nexus_types::internal_api::params::DnsRecord; use omicron_common::address::{get_64_subnet, Ipv6Subnet, RACK_PREFIX}; use omicron_common::api::external::AddressLotKind; @@ -378,26 +379,46 @@ impl super::Nexus { let first_address = IpAddr::V4(rack_network_config.infra_ip_first); let last_address = IpAddr::V4(rack_network_config.infra_ip_last); - let ipv4_block = AddressLotBlockCreate { first_address, last_address }; - - let blocks = vec![ipv4_block]; + let ipv4_block = AddressLotBlock { first_address, last_address }; let address_lot_params = AddressLotCreate { identity, kind }; - match self + let address_lot_id = match self .db_datastore .address_lot_create(opctx, &address_lot_params) .await { - Ok(_) => Ok(()), + Ok(v) => Ok(v.id()), Err(e) => match e { Error::ObjectAlreadyExists { type_name: _, object_name: _ } => { - Ok(()) + let address_lot_lookup = self.address_lot_lookup( + &opctx, + NameOrId::Name(address_lot_name.clone()), + )?; + + let (.., authz_address_lot) = address_lot_lookup + .lookup_for(authz::Action::CreateChild) + .await?; + Ok(authz_address_lot.id()) } _ => Err(e), }, }?; + match self + .db_datastore + .address_lot_block_create(opctx, address_lot_id, ipv4_block.clone()) + .await + { + Ok(_) => Ok(()), + Err(e) => match e { + Error::ObjectAlreadyExists { .. } => Ok(()), + _ => Err(Error::internal_error(&format!( + "unable to create block for address lot {address_lot_id}: {e}", + ))), + }, + }?; + let mut bgp_configs = HashMap::new(); for bgp_config in &rack_network_config.bgp { @@ -412,35 +433,36 @@ impl super::Nexus { let address_lot_name: Name = format!("as{}-lot", bgp_config.asn).parse().unwrap(); - match self + let address_lot_id = match self .db_datastore .address_lot_create( &opctx, &AddressLotCreate { identity: IdentityMetadataCreateParams { - name: address_lot_name, + name: address_lot_name.clone(), description: format!( "Address lot for announce set in as {}", bgp_config.asn ), }, kind: AddressLotKind::Infra, - // TODO: Levon - Move to new creation logic - // blocks: bgp_config - // .originate - // .iter() - // .map(|o| AddressLotBlockCreate { - // first_address: o.first_addr().into(), - // last_address: o.last_addr().into(), - // }) - // .collect(), }, ) .await { - Ok(_) => Ok(()), + Ok(v) => Ok(v.id()), Err(e) => match e { - Error::ObjectAlreadyExists { .. } => Ok(()), + Error::ObjectAlreadyExists { .. } => { + let address_lot_lookup = self.address_lot_lookup( + &opctx, + NameOrId::Name(address_lot_name), + )?; + + let (.., authz_address_lot) = address_lot_lookup + .lookup_for(authz::Action::CreateChild) + .await?; + Ok(authz_address_lot.id()) + } _ => Err(Error::internal_error(&format!( "unable to create address lot for BGP as {}: {e}", bgp_config.asn @@ -448,6 +470,30 @@ impl super::Nexus { }, }?; + for net in &bgp_config.originate { + match self + .db_datastore + .address_lot_block_create( + &opctx, + address_lot_id, + AddressLotBlock { + first_address: net.first_addr().into(), + last_address: net.last_addr().into(), + }, + ) + .await + { + Ok(_) => Ok(()), + Err(e) => match e { + Error::ObjectAlreadyExists { .. } => Ok(()), + _ => Err(Error::internal_error(&format!( + "unable to create address lot block for BGP as {}: {e}", + bgp_config.asn + ))), + }, + }?; + } + match self .db_datastore .bgp_create_announce_set( diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 83a29566985..67f34b5b168 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -258,7 +258,6 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register(networking_address_lot_create)?; api.register(networking_address_lot_delete)?; - // TODO: Levon - Operator-Accessible Address Lot Block API api.register(networking_address_lot_block_list)?; api.register(networking_address_lot_block_add)?; api.register(networking_address_lot_block_remove)?; @@ -3500,13 +3499,13 @@ async fn networking_address_lot_list( /// Add block to address lot #[endpoint { method = POST, - path = "/v1/system/networking/address-lot/{address_lot}/blocks", + path = "/v1/system/networking/address-lot/{address_lot}/blocks/add", tags = ["system/networking"], }] async fn networking_address_lot_block_add( rqctx: RequestContext, path_params: Path, - block: TypedBody, + block: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -3536,25 +3535,17 @@ async fn networking_address_lot_block_add( .await } -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct AddressLotBlockPath { - /// The address lot the block belongs to - address_lot: NameOrId, - - /// The block to delete from the address lot - block: NameOrId, -} - /// Remove block from address lot #[endpoint { - method = DELETE, - path = "/v1/system/networking/address-lot/{address_lot}/blocks/{block}", + method = POST, + path = "/v1/system/networking/address-lot/{address_lot}/blocks/remove", tags = ["system/networking"], }] async fn networking_address_lot_block_remove( rqctx: RequestContext, - path_params: Path, -) -> Result>, HttpError> { + path_params: Path, + block: TypedBody, +) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -3563,7 +3554,18 @@ async fn networking_address_lot_block_remove( let address_lot_lookup = nexus.address_lot_lookup(&opctx, path.address_lot)?; - todo!("implement address lot block remove logic") + let (.., authz_address_lot) = + address_lot_lookup.lookup_for(authz::Action::CreateChild).await?; + + nexus + .address_lot_block_delete( + &opctx, + authz_address_lot.id(), + block.into_inner(), + ) + .await?; + + Ok(HttpResponseUpdatedNoContent()) }; apictx .context diff --git a/nexus/tests/integration_tests/address_lots.rs b/nexus/tests/integration_tests/address_lots.rs index fe25daf72ee..fc5a58fc0fb 100644 --- a/nexus/tests/integration_tests/address_lots.rs +++ b/nexus/tests/integration_tests/address_lots.rs @@ -10,9 +10,7 @@ use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils::http_testing::RequestBuilder; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{ - AddressLotBlockCreate, AddressLotCreate, -}; +use nexus_types::external_api::params; use omicron_common::api::external::{ AddressLot, AddressLotBlock, AddressLotCreateResponse, AddressLotKind, IdentityMetadataCreateParams, @@ -39,7 +37,7 @@ async fn test_address_lot_basic_crud(ctx: &ControlPlaneTestContext) { assert_eq!(lots.len(), 1, "Expected one lot"); // Create a lot - let params = AddressLotCreate { + let lot_params = params::AddressLotCreate { identity: IdentityMetadataCreateParams { name: "parkinglot".parse().unwrap(), description: "an address parking lot".into(), @@ -47,10 +45,15 @@ async fn test_address_lot_basic_crud(ctx: &ControlPlaneTestContext) { kind: AddressLotKind::Infra, }; - let response: AddressLotCreateResponse = NexusRequest::objects_post( + let block_params = params::AddressLotBlock { + first_address: "203.0.113.10".parse().unwrap(), + last_address: "203.0.113.20".parse().unwrap(), + }; + + let lot_response: AddressLotCreateResponse = NexusRequest::objects_post( client, "/v1/system/networking/address-lot", - ¶ms, + &lot_params, ) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -59,43 +62,35 @@ async fn test_address_lot_basic_crud(ctx: &ControlPlaneTestContext) { .parsed_body() .unwrap(); - let address_lot = response.lot; - - let block_params = AddressLotBlockCreate { - first_address: "203.0.113.10".parse().unwrap(), - last_address: "203.0.113.20".parse().unwrap(), - }; - - NexusRequest::objects_post( + let block_response: AddressLotBlock = NexusRequest::objects_post( client, - "/v1/system/networking/address-lot/parkinglot/blocks", + &format!( + "/v1/system/networking/address-lot/{}/blocks/add", + lot_params.identity.name + ), &block_params, ) .authn_as(AuthnMode::PrivilegedUser) .execute() .await + .unwrap() + .parsed_body() .unwrap(); - // Verify there are lot blocks - let blocks = NexusRequest::iter_collection_authn::( - client, - "/v1/system/networking/address-lot/parkinglot/blocks", - "", - None, - ) - .await - .expect("Failed to list address lot blocks") - .all_items; + let address_lot = lot_response.lot; + + assert_eq!(address_lot.identity.name, lot_params.identity.name); + assert_eq!( + address_lot.identity.description, + lot_params.identity.description + ); - assert_eq!(address_lot.identity.name, params.identity.name); - assert_eq!(address_lot.identity.description, params.identity.description); - assert_eq!(blocks.len(), 1, "Expected 1 address lot block"); assert_eq!( - blocks[0].first_address, + block_response.first_address, "203.0.113.10".parse::().unwrap() ); assert_eq!( - blocks[0].last_address, + block_response.last_address, "203.0.113.20".parse::().unwrap() ); @@ -112,6 +107,22 @@ async fn test_address_lot_basic_crud(ctx: &ControlPlaneTestContext) { assert_eq!(lots.len(), 2, "Expected 2 lots"); assert_eq!(lots[1], address_lot); + + // Verify there are lot blocks + let blist = NexusRequest::iter_collection_authn::( + client, + &format!( + "/v1/system/networking/address-lot/{}/blocks", + lot_params.identity.name + ), + "", + None, + ) + .await + .expect("Failed to list address lot blocks") + .all_items; + + assert_eq!(blist.len(), 1, "Expected 1 address lot block"); } #[nexus_test] @@ -122,14 +133,14 @@ async fn test_address_lot_invalid_range(ctx: &ControlPlaneTestContext) { // Try to create a lot with different address families params.push(( - AddressLotCreate { + params::AddressLotCreate { identity: IdentityMetadataCreateParams { name: "family".parse().unwrap(), description: "an address parking lot".into(), }, kind: AddressLotKind::Infra, }, - AddressLotBlockCreate { + params::AddressLotBlock { first_address: "203.0.113.10".parse().unwrap(), last_address: "fd00:1701::d".parse().unwrap(), }, @@ -137,14 +148,14 @@ async fn test_address_lot_invalid_range(ctx: &ControlPlaneTestContext) { // Try to create an IPv4 lot where the first address comes after the second. params.push(( - AddressLotCreate { + params::AddressLotCreate { identity: IdentityMetadataCreateParams { name: "v4".parse().unwrap(), description: "an address parking lot".into(), }, kind: AddressLotKind::Infra, }, - AddressLotBlockCreate { + params::AddressLotBlock { first_address: "203.0.113.20".parse().unwrap(), last_address: "203.0.113.10".parse().unwrap(), }, @@ -152,27 +163,27 @@ async fn test_address_lot_invalid_range(ctx: &ControlPlaneTestContext) { // Try to create an IPv6 lot where the first address comes after the second. params.push(( - AddressLotCreate { + params::AddressLotCreate { identity: IdentityMetadataCreateParams { name: "v6".parse().unwrap(), description: "an address parking lot".into(), }, kind: AddressLotKind::Infra, }, - AddressLotBlockCreate { + params::AddressLotBlock { first_address: "fd00:1701::d".parse().unwrap(), last_address: "fd00:1701::a".parse().unwrap(), }, )); - for (lot_params, block_params) in ¶ms { + for (address_lot_params, address_lot_block_params) in ¶ms { NexusRequest::new( RequestBuilder::new( client, Method::POST, "/v1/system/networking/address-lot", ) - .body(Some(&lot_params)) + .body(Some(&address_lot_params)) .expect_status(Some(StatusCode::CREATED)), ) .authn_as(AuthnMode::PrivilegedUser) @@ -185,11 +196,11 @@ async fn test_address_lot_invalid_range(ctx: &ControlPlaneTestContext) { client, Method::POST, &format!( - "/v1/system/networking/address-lot/{}/blocks", - lot_params.identity.name + "/v1/system/networking/address-lot/{}/blocks/add", + address_lot_params.identity.name ), ) - .body(Some(&block_params)) + .body(Some(&address_lot_block_params)) .expect_status(Some(StatusCode::BAD_REQUEST)), ) .authn_as(AuthnMode::PrivilegedUser) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 85888975d68..921e5f68c78 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -550,6 +550,10 @@ pub const DEMO_ADDRESS_LOT_URL: &'static str = "/v1/system/networking/address-lot/parkinglot"; pub const DEMO_ADDRESS_LOT_BLOCKS_URL: &'static str = "/v1/system/networking/address-lot/parkinglot/blocks"; +pub const DEMO_ADDRESS_LOT_BLOCK_ADD_URL: &'static str = + "/v1/system/networking/address-lot/parkinglot/blocks/add"; +pub const DEMO_ADDRESS_LOT_BLOCK_REMOVE_URL: &'static str = + "/v1/system/networking/address-lot/parkinglot/blocks/remove"; pub static DEMO_ADDRESS_LOT_CREATE: Lazy = Lazy::new(|| params::AddressLotCreate { identity: IdentityMetadataCreateParams { @@ -559,8 +563,8 @@ pub static DEMO_ADDRESS_LOT_CREATE: Lazy = kind: AddressLotKind::Infra, }); -pub static DEMO_ADDRESS_LOT_BLOCK_CREATE: Lazy = - Lazy::new(|| params::AddressLotBlockCreate { +pub static DEMO_ADDRESS_LOT_BLOCK_CREATE: Lazy = + Lazy::new(|| params::AddressLotBlock { first_address: "203.0.113.10".parse().unwrap(), last_address: "203.0.113.20".parse().unwrap(), }); @@ -2230,15 +2234,43 @@ pub static VERIFY_ENDPOINTS: Lazy> = Lazy::new(|| { ], }, + VerifyEndpoint { + url: &DEMO_ADDRESS_LOT_BLOCK_ADD_URL, + visibility: Visibility::Protected, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value(&*DEMO_ADDRESS_LOT_BLOCK_CREATE).unwrap(), + ), + ], + }, + VerifyEndpoint { url: &DEMO_ADDRESS_LOT_BLOCKS_URL, visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + ], + }, + + VerifyEndpoint { + url: &DEMO_ADDRESS_LOT_BLOCK_REMOVE_URL, + visibility: Visibility::Protected, + unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![ AllowedMethod::Post( serde_json::to_value(&*DEMO_ADDRESS_LOT_BLOCK_CREATE).unwrap(), ), - AllowedMethod::Get + ], + }, + + VerifyEndpoint { + url: &DEMO_ADDRESS_LOT_URL, + visibility: Visibility::Protected, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Delete, ], }, diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index b49d7d43990..f63bae38c4b 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -9,7 +9,7 @@ use http::StatusCode; use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::params::{ - Address, AddressConfig, AddressLotBlockCreate, AddressLotCreate, + Address, AddressConfig, AddressLotBlock, AddressLotCreate, BgpAnnounceSetCreate, BgpAnnouncementCreate, BgpConfigCreate, BgpPeerConfig, LinkConfigCreate, LldpServiceConfigCreate, Route, RouteConfig, SwitchInterfaceConfigCreate, SwitchInterfaceKind, @@ -43,11 +43,11 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { }; let block_params = vec![ - AddressLotBlockCreate { + AddressLotBlock { first_address: "203.0.113.10".parse().unwrap(), last_address: "203.0.113.20".parse().unwrap(), }, - AddressLotBlockCreate { + AddressLotBlock { first_address: "1.2.3.0".parse().unwrap(), last_address: "1.2.3.255".parse().unwrap(), }, diff --git a/nexus/tests/integration_tests/unauthorized.rs b/nexus/tests/integration_tests/unauthorized.rs index 1d7018e271a..92d70de2e0d 100644 --- a/nexus/tests/integration_tests/unauthorized.rs +++ b/nexus/tests/integration_tests/unauthorized.rs @@ -222,7 +222,7 @@ static SETUP_REQUESTS: Lazy> = Lazy::new(|| { }, // Create the default Address Lot Block SetupReq::Post { - url: &DEMO_ADDRESS_LOT_BLOCKS_URL, + url: &DEMO_ADDRESS_LOT_BLOCK_ADD_URL, body: serde_json::to_value(&*DEMO_ADDRESS_LOT_BLOCK_CREATE) .unwrap(), id_routes: vec![], diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 68b7fdfdc42..c8861967381 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -170,9 +170,9 @@ ip_pool_silo_update PUT /v1/system/ip-pools/{pool}/sil ip_pool_update PUT /v1/system/ip-pools/{pool} ip_pool_utilization_view GET /v1/system/ip-pools/{pool}/utilization ip_pool_view GET /v1/system/ip-pools/{pool} -networking_address_lot_block_add POST /v1/system/networking/address-lot/{address_lot}/blocks +networking_address_lot_block_add POST /v1/system/networking/address-lot/{address_lot}/blocks/add networking_address_lot_block_list GET /v1/system/networking/address-lot/{address_lot}/blocks -networking_address_lot_block_remove DELETE /v1/system/networking/address-lot/{address_lot}/blocks/{block} +networking_address_lot_block_remove POST /v1/system/networking/address-lot/{address_lot}/blocks/remove networking_address_lot_create POST /v1/system/networking/address-lot networking_address_lot_delete DELETE /v1/system/networking/address-lot/{address_lot} networking_address_lot_list GET /v1/system/networking/address-lot diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index 39e5b76448d..c5091c5a3bc 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -1,6 +1,5 @@ API endpoints with no coverage in authz tests: probe_delete (delete "/experimental/v1/probes/{probe}") -networking_address_lot_block_remove (delete "/v1/system/networking/address-lot/{address_lot}/blocks/{block}") probe_list (get "/experimental/v1/probes") probe_view (get "/experimental/v1/probes/{probe}") ping (get "/v1/ping") diff --git a/nexus/tests/output/unexpected-authz-endpoints.txt b/nexus/tests/output/unexpected-authz-endpoints.txt index cd05058762e..23235ecf641 100644 --- a/nexus/tests/output/unexpected-authz-endpoints.txt +++ b/nexus/tests/output/unexpected-authz-endpoints.txt @@ -1,3 +1,4 @@ API endpoints tested by unauthorized.rs but not found in the OpenAPI spec: PUT "/v1/system/update/repository?file_name=demo-repo.zip" GET "/v1/system/update/repository/1.0.0" +DELETE "/v1/system/networking/address-lot/parkinglot" diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 232028f0908..336f0440709 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1434,7 +1434,7 @@ pub struct AddressLotCreate { /// Parameters for creating an address lot block. Fist and last addresses are /// inclusive. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AddressLotBlockCreate { +pub struct AddressLotBlock { /// The first address in the lot (inclusive). pub first_address: IpAddr, /// The last address in the lot (inclusive). diff --git a/openapi/nexus.json b/openapi/nexus.json index f2676f80b2d..854c01053b4 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -6303,7 +6303,9 @@ "x-dropshot-pagination": { "required": [] } - }, + } + }, + "/v1/system/networking/address-lot/{address_lot}/blocks/add": { "post": { "tags": [ "system/networking" @@ -6325,7 +6327,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddressLotBlockCreate" + "$ref": "#/components/schemas/AddressLotBlock2" } } }, @@ -6351,8 +6353,8 @@ } } }, - "/v1/system/networking/address-lot/{address_lot}/blocks/{block}": { - "delete": { + "/v1/system/networking/address-lot/{address_lot}/blocks/remove": { + "post": { "tags": [ "system/networking" ], @@ -6362,33 +6364,27 @@ { "in": "path", "name": "address_lot", - "description": "The address lot the block belongs to", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, - { - "in": "path", - "name": "block", - "description": "The block to delete from the address lot", + "description": "Name or ID of the address lot", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" } } ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddressLotBlockResultsPage" - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressLotBlock2" } } }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, "4XX": { "$ref": "#/components/responses/Error" }, @@ -9820,7 +9816,7 @@ "last_address" ] }, - "AddressLotBlockCreate": { + "AddressLotBlock2": { "description": "Parameters for creating an address lot block. Fist and last addresses are inclusive.", "type": "object", "properties": { From 812e991cbea57b6ce845a53688e37738eda4fcc8 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Wed, 17 Jul 2024 21:20:29 +0000 Subject: [PATCH 04/37] rename AddressLotBlock params --- nexus/db-queries/src/db/datastore/address_lot.rs | 4 ++-- nexus/src/app/address_lot.rs | 8 +++++--- nexus/src/app/rack.rs | 7 ++++--- nexus/src/external_api/http_entrypoints.rs | 4 ++-- nexus/tests/integration_tests/address_lots.rs | 8 ++++---- nexus/tests/integration_tests/endpoints.rs | 11 ++++++----- nexus/tests/integration_tests/switch_port.rs | 6 +++--- nexus/types/src/external_api/params.rs | 6 +++--- openapi/nexus.json | 8 ++++---- 9 files changed, 33 insertions(+), 29 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/address_lot.rs b/nexus/db-queries/src/db/datastore/address_lot.rs index 38fb90e58c7..6769feaa6ab 100644 --- a/nexus/db-queries/src/db/datastore/address_lot.rs +++ b/nexus/db-queries/src/db/datastore/address_lot.rs @@ -202,7 +202,7 @@ impl DataStore { &self, opctx: &OpContext, address_lot_id: Uuid, - params: params::AddressLotBlock, + params: params::AddressLotBlockAddRemove, ) -> CreateResult { use db::schema::address_lot_block::dsl; @@ -265,7 +265,7 @@ impl DataStore { &self, opctx: &OpContext, address_lot_id: Uuid, - params: params::AddressLotBlock, + params: params::AddressLotBlockAddRemove, ) -> DeleteResult { use db::schema::address_lot_block::dsl; use db::schema::address_lot_rsvd_block::dsl as rsvd_block_dsl; diff --git a/nexus/src/app/address_lot.rs b/nexus/src/app/address_lot.rs index 8289dab14c4..8757f6c92d0 100644 --- a/nexus/src/app/address_lot.rs +++ b/nexus/src/app/address_lot.rs @@ -72,7 +72,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, address_lot_id: Uuid, - block: params::AddressLotBlock, + block: params::AddressLotBlockAddRemove, ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; validate_block(&block)?; @@ -85,7 +85,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, address_lot_id: Uuid, - block: params::AddressLotBlock, + block: params::AddressLotBlockAddRemove, ) -> DeleteResult { opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; validate_block(&block)?; @@ -108,7 +108,9 @@ impl super::Nexus { } } -fn validate_block(block: ¶ms::AddressLotBlock) -> Result<(), Error> { +fn validate_block( + block: ¶ms::AddressLotBlockAddRemove, +) -> Result<(), Error> { match (&block.first_address, &block.last_address) { (IpAddr::V4(first), IpAddr::V4(last)) => { validate_v4_block(first, last)? diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index be5d247597f..f62353b4886 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -28,7 +28,7 @@ use nexus_types::deployment::CockroachDbClusterVersion; use nexus_types::deployment::SledFilter; use nexus_types::external_api::params::Address; use nexus_types::external_api::params::AddressConfig; -use nexus_types::external_api::params::AddressLotBlock; +use nexus_types::external_api::params::AddressLotBlockAddRemove; use nexus_types::external_api::params::BgpAnnounceSetCreate; use nexus_types::external_api::params::BgpAnnouncementCreate; use nexus_types::external_api::params::BgpConfigCreate; @@ -379,7 +379,8 @@ impl super::Nexus { let first_address = IpAddr::V4(rack_network_config.infra_ip_first); let last_address = IpAddr::V4(rack_network_config.infra_ip_last); - let ipv4_block = AddressLotBlock { first_address, last_address }; + let ipv4_block = + AddressLotBlockAddRemove { first_address, last_address }; let address_lot_params = AddressLotCreate { identity, kind }; @@ -476,7 +477,7 @@ impl super::Nexus { .address_lot_block_create( &opctx, address_lot_id, - AddressLotBlock { + AddressLotBlockAddRemove { first_address: net.first_addr().into(), last_address: net.last_addr().into(), }, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 67f34b5b168..0b2d596e21c 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -3505,7 +3505,7 @@ async fn networking_address_lot_list( async fn networking_address_lot_block_add( rqctx: RequestContext, path_params: Path, - block: TypedBody, + block: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -3544,7 +3544,7 @@ async fn networking_address_lot_block_add( async fn networking_address_lot_block_remove( rqctx: RequestContext, path_params: Path, - block: TypedBody, + block: TypedBody, ) -> Result { let apictx = rqctx.context(); let handler = async { diff --git a/nexus/tests/integration_tests/address_lots.rs b/nexus/tests/integration_tests/address_lots.rs index fc5a58fc0fb..69ac2f06939 100644 --- a/nexus/tests/integration_tests/address_lots.rs +++ b/nexus/tests/integration_tests/address_lots.rs @@ -45,7 +45,7 @@ async fn test_address_lot_basic_crud(ctx: &ControlPlaneTestContext) { kind: AddressLotKind::Infra, }; - let block_params = params::AddressLotBlock { + let block_params = params::AddressLotBlockAddRemove { first_address: "203.0.113.10".parse().unwrap(), last_address: "203.0.113.20".parse().unwrap(), }; @@ -140,7 +140,7 @@ async fn test_address_lot_invalid_range(ctx: &ControlPlaneTestContext) { }, kind: AddressLotKind::Infra, }, - params::AddressLotBlock { + params::AddressLotBlockAddRemove { first_address: "203.0.113.10".parse().unwrap(), last_address: "fd00:1701::d".parse().unwrap(), }, @@ -155,7 +155,7 @@ async fn test_address_lot_invalid_range(ctx: &ControlPlaneTestContext) { }, kind: AddressLotKind::Infra, }, - params::AddressLotBlock { + params::AddressLotBlockAddRemove { first_address: "203.0.113.20".parse().unwrap(), last_address: "203.0.113.10".parse().unwrap(), }, @@ -170,7 +170,7 @@ async fn test_address_lot_invalid_range(ctx: &ControlPlaneTestContext) { }, kind: AddressLotKind::Infra, }, - params::AddressLotBlock { + params::AddressLotBlockAddRemove { first_address: "fd00:1701::d".parse().unwrap(), last_address: "fd00:1701::a".parse().unwrap(), }, diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 921e5f68c78..ebf2528de87 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -563,11 +563,12 @@ pub static DEMO_ADDRESS_LOT_CREATE: Lazy = kind: AddressLotKind::Infra, }); -pub static DEMO_ADDRESS_LOT_BLOCK_CREATE: Lazy = - Lazy::new(|| params::AddressLotBlock { - first_address: "203.0.113.10".parse().unwrap(), - last_address: "203.0.113.20".parse().unwrap(), - }); +pub static DEMO_ADDRESS_LOT_BLOCK_CREATE: Lazy< + params::AddressLotBlockAddRemove, +> = Lazy::new(|| params::AddressLotBlockAddRemove { + first_address: "203.0.113.10".parse().unwrap(), + last_address: "203.0.113.20".parse().unwrap(), +}); pub const DEMO_BGP_CONFIG_CREATE_URL: &'static str = "/v1/system/networking/bgp?name_or_id=as47"; diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index f63bae38c4b..e6b6e01b2c7 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -9,7 +9,7 @@ use http::StatusCode; use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::params::{ - Address, AddressConfig, AddressLotBlock, AddressLotCreate, + Address, AddressConfig, AddressLotBlockAddRemove, AddressLotCreate, BgpAnnounceSetCreate, BgpAnnouncementCreate, BgpConfigCreate, BgpPeerConfig, LinkConfigCreate, LldpServiceConfigCreate, Route, RouteConfig, SwitchInterfaceConfigCreate, SwitchInterfaceKind, @@ -43,11 +43,11 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { }; let block_params = vec![ - AddressLotBlock { + AddressLotBlockAddRemove { first_address: "203.0.113.10".parse().unwrap(), last_address: "203.0.113.20".parse().unwrap(), }, - AddressLotBlock { + AddressLotBlockAddRemove { first_address: "1.2.3.0".parse().unwrap(), last_address: "1.2.3.255".parse().unwrap(), }, diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 336f0440709..75e60166437 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1431,10 +1431,10 @@ pub struct AddressLotCreate { pub kind: AddressLotKind, } -/// Parameters for creating an address lot block. Fist and last addresses are -/// inclusive. +/// Parameters for adding or removing an address lot block. +/// First and last addresses are inclusive. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AddressLotBlock { +pub struct AddressLotBlockAddRemove { /// The first address in the lot (inclusive). pub first_address: IpAddr, /// The last address in the lot (inclusive). diff --git a/openapi/nexus.json b/openapi/nexus.json index 854c01053b4..44a1ff0950b 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -6327,7 +6327,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddressLotBlock2" + "$ref": "#/components/schemas/AddressLotBlockAddRemove" } } }, @@ -6375,7 +6375,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddressLotBlock2" + "$ref": "#/components/schemas/AddressLotBlockAddRemove" } } }, @@ -9816,8 +9816,8 @@ "last_address" ] }, - "AddressLotBlock2": { - "description": "Parameters for creating an address lot block. Fist and last addresses are inclusive.", + "AddressLotBlockAddRemove": { + "description": "Parameters for adding or removing an address lot block. First and last addresses are inclusive.", "type": "object", "properties": { "first_address": { From 047c6fe5fbdc484647c77579b13f4550bd2b3a57 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Tue, 23 Jul 2024 21:35:58 +0000 Subject: [PATCH 05/37] WIP: allow partial update of switch port configuration --- nexus/src/external_api/http_entrypoints.rs | 278 +++++++++++++++++++-- nexus/tests/integration_tests/endpoints.rs | 4 +- nexus/tests/output/nexus_tags.txt | 8 +- nexus/types/src/external_api/params.rs | 12 +- 4 files changed, 279 insertions(+), 23 deletions(-) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index e91fc6d823d..b3395fbac68 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -41,8 +41,6 @@ use nexus_db_queries::db::lookup::ImageLookup; use nexus_db_queries::db::lookup::ImageParentLookup; use nexus_db_queries::db::model::Name; use nexus_types::external_api::shared::{BfdStatus, ProbeInfo}; -use omicron_common::api::external::http_pagination::marker_for_name; -use omicron_common::api::external::http_pagination::marker_for_name_or_id; use omicron_common::api::external::http_pagination::name_or_id_pagination; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::http_pagination::PaginatedById; @@ -81,6 +79,12 @@ use omicron_common::api::external::VpcFirewallRules; use omicron_common::api::external::{ http_pagination::data_page_params_for, AggregateBgpMessageHistory, }; +use omicron_common::api::external::{ + http_pagination::marker_for_name, SwitchPortGeometry, +}; +use omicron_common::api::external::{ + http_pagination::marker_for_name_or_id, SwitchPortLinkConfig, +}; use omicron_common::bail_unless; use omicron_uuid_kinds::GenericUuid; use parse_display::Display; @@ -268,15 +272,70 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register(networking_loopback_address_delete)?; api.register(networking_loopback_address_list)?; - api.register(networking_switch_port_settings_list)?; - api.register(networking_switch_port_settings_view)?; - api.register(networking_switch_port_settings_create)?; - api.register(networking_switch_port_settings_delete)?; + api.register(networking_switch_port_configuration_create)?; + api.register(networking_switch_port_configuration_delete)?; + api.register(networking_switch_port_configuration_view)?; + api.register(networking_switch_port_configuration_list)?; + + // TODO: Levon - Group composition (omicron#4405)? + // /v1/system/networking/switch-port-configuration-group/{name_or_id}/.. + + // /v1/system/networking/switch-port-configuration/{name_or_id}/geometry + // TODO: Levon - test + api.register(networking_switch_port_configuration_geometry_view)?; + // TODO: Levon - test + api.register(networking_switch_port_configuration_geometry_set)?; + + // /v1/system/networking/switch-port-configuration/{name_or_id}/link + // TODO: Levon - test + api.register(networking_switch_port_configuration_link_create)?; + // TODO: Levon - test + api.register(networking_switch_port_configuration_link_delete)?; + // TODO: Levon - test + api.register(networking_switch_port_configuration_link_view)?; + // TODO: Levon - test + api.register(networking_switch_port_configuration_link_list)?; + + // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_create)?; + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_delete)?; + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_view)?; + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_list)?; + + // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/address + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_addr_add)?; + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_addr_remove)?; + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_addr_list)?; + + // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/route + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_route_add)?; + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_route_remove)?; + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_route_list)?; + + // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/bgp_peer + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_bgp_peer_add)?; + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_bgp_peer_remove)?; + // TODO: Levon - test + // api.register(networking_switch_port_configuration_link_interface_bgp_peer_list)?; api.register(networking_switch_port_list)?; api.register(networking_switch_port_status)?; api.register(networking_switch_port_apply_settings)?; api.register(networking_switch_port_clear_settings)?; + // TODO: Levon - test + // api.register(networking_switch_port_view_settings)?; api.register(networking_bgp_config_create)?; api.register(networking_bgp_config_list)?; @@ -3739,10 +3798,10 @@ async fn networking_loopback_address_list( /// Create switch port settings #[endpoint { method = POST, - path = "/v1/system/networking/switch-port-settings", + path = "/v1/system/networking/switch-port-configuration", tags = ["system/networking"], }] -async fn networking_switch_port_settings_create( +async fn networking_switch_port_configuration_create( rqctx: RequestContext, new_settings: TypedBody, ) -> Result, HttpError> { @@ -3766,10 +3825,10 @@ async fn networking_switch_port_settings_create( /// Delete switch port settings #[endpoint { method = DELETE, - path = "/v1/system/networking/switch-port-settings", + path = "/v1/system/networking/switch-port-configuration", tags = ["system/networking"], }] -async fn networking_switch_port_settings_delete( +async fn networking_switch_port_configuration_delete( rqctx: RequestContext, query_params: Query, ) -> Result { @@ -3791,10 +3850,10 @@ async fn networking_switch_port_settings_delete( /// List switch port settings #[endpoint { method = GET, - path = "/v1/system/networking/switch-port-settings", + path = "/v1/system/networking/switch-port-configuration", tags = ["system/networking"], }] -async fn networking_switch_port_settings_list( +async fn networking_switch_port_configuration_list( rqctx: RequestContext, query_params: Query< PaginatedByNameOrId, @@ -3828,20 +3887,20 @@ async fn networking_switch_port_settings_list( .await } -/// Get information about switch port +/// Get information about a named set of switch-port-settings #[endpoint { method = GET, - path = "/v1/system/networking/switch-port-settings/{port}", + path = "/v1/system/networking/switch-port-configuration/{name_or_id}", tags = ["system/networking"], }] -async fn networking_switch_port_settings_view( +async fn networking_switch_port_configuration_view( rqctx: RequestContext, path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().port; + let query = path_params.into_inner().name_or_id; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; Ok(HttpResponseOk(settings.into())) @@ -3853,6 +3912,193 @@ async fn networking_switch_port_settings_view( .await } +/// Get switch port geometry for a provided switch port configuration +#[endpoint { + method = GET, + path = "/v1/system/networking/switch-port-configuration/{name_or_id}/geometry", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_geometry_view( + rqctx: RequestContext, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().name_or_id; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("fetch geometry") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Set switch port geometry for a provided switch port configuration +#[endpoint { + method = POST, + path = "/v1/system/networking/switch-port-configuration/{name_or_id}/geometry", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_geometry_set( + rqctx: RequestContext, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().name_or_id; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("set geometry") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// List links for a provided switch port configuration +#[endpoint { + method = GET, + path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_list( + rqctx: RequestContext, + //query_params: Query>, + path_params: Path, + // ) -> Result>, HttpError> { +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().name_or_id; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("list links") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Create a link for a provided switch port configuration +#[endpoint { + method = POST, + path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_create( + rqctx: RequestContext, + path_params: Path, + new_settings: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().name_or_id; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("create link") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// View a link for a provided switch port configuration +#[endpoint { + method = GET, + path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_view( + rqctx: RequestContext, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().name_or_id; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("view link") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Delete a link for a provided switch port configuration +#[endpoint { + method = DELETE, + path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_delete( + rqctx: RequestContext, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().name_or_id; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("delete link") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// List interfaces for a provided switch port link configuration +#[endpoint { + method = GET, + path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_list( + rqctx: RequestContext, + //query_params: Query>, + path_params: Path, + // ) -> Result>, HttpError> { +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().name_or_id; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("list interfaces") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + /// List switch ports #[endpoint { method = GET, diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 6da42298de2..ba4c0419880 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -532,9 +532,9 @@ pub static DEMO_LOOPBACK_CREATE: Lazy = }); pub const DEMO_SWITCH_PORT_SETTINGS_URL: &'static str = - "/v1/system/networking/switch-port-settings?port_settings=portofino"; + "/v1/system/networking/switch-port-configuration?port_settings=portofino"; pub const DEMO_SWITCH_PORT_SETTINGS_INFO_URL: &'static str = - "/v1/system/networking/switch-port-settings/protofino"; + "/v1/system/networking/switch-port-configuration/protofino"; pub static DEMO_SWITCH_PORT_SETTINGS_CREATE: Lazy< params::SwitchPortSettingsCreate, > = Lazy::new(|| { diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 83cdf42ff2b..d32ac505dc1 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -193,10 +193,10 @@ networking_bgp_status GET /v1/system/networking/bgp-stat networking_loopback_address_create POST /v1/system/networking/loopback-address networking_loopback_address_delete DELETE /v1/system/networking/loopback-address/{rack_id}/{switch_location}/{address}/{subnet_mask} networking_loopback_address_list GET /v1/system/networking/loopback-address -networking_switch_port_settings_create POST /v1/system/networking/switch-port-settings -networking_switch_port_settings_delete DELETE /v1/system/networking/switch-port-settings -networking_switch_port_settings_list GET /v1/system/networking/switch-port-settings -networking_switch_port_settings_view GET /v1/system/networking/switch-port-settings/{port} +networking_switch_port_configuration_create POST /v1/system/networking/switch-port-configuration +networking_switch_port_configuration_delete DELETE /v1/system/networking/switch-port-configuration +networking_switch_port_configuration_list GET /v1/system/networking/switch-port-configuration +networking_switch_port_configuration_view GET /v1/system/networking/switch-port-configuration/{name_or_id} API operations found with tag "system/silos" OPERATION ID METHOD URL PATH diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 4505f08f24d..434ac38f0bb 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1788,7 +1788,17 @@ pub struct SwitchPortSettingsSelector { #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct SwitchPortSettingsInfoSelector { /// A name or id to use when selecting switch port settings info objects. - pub port: NameOrId, + pub name_or_id: NameOrId, +} + +/// Select a link settings i nfo object by port settings name and link name. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortSettingsLinkInfoSelector { + /// A name or id to use when selecting switch port settings info objects. + pub name_or_id: NameOrId, + + /// Link name + pub link: Name, } /// Select a switch port by name. From 8a807d93ef5b70011934e3b466b18c20c524890b Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Mon, 29 Jul 2024 19:53:31 +0000 Subject: [PATCH 06/37] fixup! WIP: allow partial update of switch port configuration --- .../src/db/datastore/switch_port.rs | 4 +- nexus/src/external_api/http_entrypoints.rs | 434 ++++++- nexus/tests/output/nexus_tags.txt | 21 +- .../output/uncovered-authz-endpoints.txt | 19 + nexus/types/src/external_api/params.rs | 34 +- openapi/nexus.json | 1156 ++++++++++++++++- 6 files changed, 1578 insertions(+), 90 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 159933dce00..315452c910f 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -248,7 +248,7 @@ impl DataStore { ) -> DeleteResult { let conn = self.pool_connection_authorized(opctx).await?; - let selector = match ¶ms.port_settings { + let selector = match ¶ms.configuration { None => return Err(Error::invalid_request("name or id required")), Some(name_or_id) => name_or_id, }; @@ -273,7 +273,7 @@ impl DataStore { } } } else { - let name = match ¶ms.port_settings { + let name = match ¶ms.configuration { Some(name_or_id) => name_or_id.to_string(), None => String::new(), }; diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index b3395fbac68..2aaa6fecc44 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -40,9 +40,11 @@ use nexus_db_queries::db::identity::Resource; use nexus_db_queries::db::lookup::ImageLookup; use nexus_db_queries::db::lookup::ImageParentLookup; use nexus_db_queries::db::model::Name; -use nexus_types::external_api::shared::{BfdStatus, ProbeInfo}; -use omicron_common::api::external::http_pagination::name_or_id_pagination; -use omicron_common::api::external::http_pagination::PaginatedBy; +use nexus_types::external_api::{ + params::{BgpPeerConfig, RouteConfig}, + shared::{BfdStatus, ProbeInfo}, +}; +use omicron_common::api::external::http_pagination::marker_for_name; use omicron_common::api::external::http_pagination::PaginatedById; use omicron_common::api::external::http_pagination::PaginatedByName; use omicron_common::api::external::http_pagination::PaginatedByNameOrId; @@ -70,6 +72,7 @@ use omicron_common::api::external::Probe; use omicron_common::api::external::RouterRoute; use omicron_common::api::external::RouterRouteKind; use omicron_common::api::external::SwitchPort; +use omicron_common::api::external::SwitchPortGeometry; use omicron_common::api::external::SwitchPortSettings; use omicron_common::api::external::SwitchPortSettingsView; use omicron_common::api::external::TufRepoGetResponse; @@ -80,11 +83,12 @@ use omicron_common::api::external::{ http_pagination::data_page_params_for, AggregateBgpMessageHistory, }; use omicron_common::api::external::{ - http_pagination::marker_for_name, SwitchPortGeometry, + http_pagination::marker_for_name_or_id, SwitchPortLinkConfig, }; use omicron_common::api::external::{ - http_pagination::marker_for_name_or_id, SwitchPortLinkConfig, + http_pagination::name_or_id_pagination, SwitchInterfaceConfig, }; +use omicron_common::api::external::{http_pagination::PaginatedBy, BgpPeer}; use omicron_common::bail_unless; use omicron_uuid_kinds::GenericUuid; use parse_display::Display; @@ -298,37 +302,59 @@ pub(crate) fn external_api() -> NexusApiDescription { // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_create)?; + api.register( + networking_switch_port_configuration_link_interface_create, + )?; // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_delete)?; + api.register( + networking_switch_port_configuration_link_interface_delete, + )?; // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_view)?; + api.register(networking_switch_port_configuration_link_interface_view)?; // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_list)?; + api.register(networking_switch_port_configuration_link_interface_list)?; // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/address // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_addr_add)?; + api.register( + networking_switch_port_configuration_link_interface_address_add, + )?; // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_addr_remove)?; + api.register( + networking_switch_port_configuration_link_interface_address_remove, + )?; // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_addr_list)?; + api.register( + networking_switch_port_configuration_link_interface_address_list, + )?; // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/route // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_route_add)?; + api.register( + networking_switch_port_configuration_link_interface_route_add, + )?; // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_route_remove)?; + api.register( + networking_switch_port_configuration_link_interface_route_remove, + )?; // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_route_list)?; + api.register( + networking_switch_port_configuration_link_interface_route_list, + )?; - // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/bgp_peer + // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/bgp-peer // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_bgp_peer_add)?; + api.register( + networking_switch_port_configuration_link_interface_bgp_peer_add, + )?; // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_bgp_peer_remove)?; + api.register( + networking_switch_port_configuration_link_interface_bgp_peer_remove, + )?; // TODO: Levon - test - // api.register(networking_switch_port_configuration_link_interface_bgp_peer_list)?; + api.register( + networking_switch_port_configuration_link_interface_bgp_peer_list, + )?; api.register(networking_switch_port_list)?; api.register(networking_switch_port_status)?; @@ -3890,7 +3916,7 @@ async fn networking_switch_port_configuration_list( /// Get information about a named set of switch-port-settings #[endpoint { method = GET, - path = "/v1/system/networking/switch-port-configuration/{name_or_id}", + path = "/v1/system/networking/switch-port-configuration/{configuration}", tags = ["system/networking"], }] async fn networking_switch_port_configuration_view( @@ -3900,7 +3926,7 @@ async fn networking_switch_port_configuration_view( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().name_or_id; + let query = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; Ok(HttpResponseOk(settings.into())) @@ -3915,7 +3941,7 @@ async fn networking_switch_port_configuration_view( /// Get switch port geometry for a provided switch port configuration #[endpoint { method = GET, - path = "/v1/system/networking/switch-port-configuration/{name_or_id}/geometry", + path = "/v1/system/networking/switch-port-configuration/{configuration}/geometry", tags = ["system/networking"], }] async fn networking_switch_port_configuration_geometry_view( @@ -3925,7 +3951,7 @@ async fn networking_switch_port_configuration_geometry_view( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().name_or_id; + let query = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; @@ -3941,17 +3967,18 @@ async fn networking_switch_port_configuration_geometry_view( /// Set switch port geometry for a provided switch port configuration #[endpoint { method = POST, - path = "/v1/system/networking/switch-port-configuration/{name_or_id}/geometry", + path = "/v1/system/networking/switch-port-configuration/{configuration}/geometry", tags = ["system/networking"], }] async fn networking_switch_port_configuration_geometry_set( rqctx: RequestContext, path_params: Path, -) -> Result, HttpError> { + new_settings: TypedBody, +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().name_or_id; + let query = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; @@ -3967,19 +3994,18 @@ async fn networking_switch_port_configuration_geometry_set( /// List links for a provided switch port configuration #[endpoint { method = GET, - path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link", + path = "/v1/system/networking/switch-port-configuration/{configuration}/link", tags = ["system/networking"], }] async fn networking_switch_port_configuration_link_list( rqctx: RequestContext, - //query_params: Query>, path_params: Path, - // ) -> Result>, HttpError> { -) -> Result, HttpError> { + // omitting pagination should be ok since there are a small number of possible links +) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().name_or_id; + let query = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; @@ -3995,18 +4021,18 @@ async fn networking_switch_port_configuration_link_list( /// Create a link for a provided switch port configuration #[endpoint { method = POST, - path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link", + path = "/v1/system/networking/switch-port-configuration/{configuration}/link", tags = ["system/networking"], }] async fn networking_switch_port_configuration_link_create( rqctx: RequestContext, path_params: Path, new_settings: TypedBody, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().name_or_id; + let query = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; @@ -4022,7 +4048,7 @@ async fn networking_switch_port_configuration_link_create( /// View a link for a provided switch port configuration #[endpoint { method = GET, - path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}", + path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}", tags = ["system/networking"], }] async fn networking_switch_port_configuration_link_view( @@ -4032,7 +4058,7 @@ async fn networking_switch_port_configuration_link_view( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().name_or_id; + let query = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; @@ -4048,17 +4074,17 @@ async fn networking_switch_port_configuration_link_view( /// Delete a link for a provided switch port configuration #[endpoint { method = DELETE, - path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}", + path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}", tags = ["system/networking"], }] async fn networking_switch_port_configuration_link_delete( rqctx: RequestContext, path_params: Path, -) -> Result, HttpError> { +) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().name_or_id; + let query = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; @@ -4074,19 +4100,20 @@ async fn networking_switch_port_configuration_link_delete( /// List interfaces for a provided switch port link configuration #[endpoint { method = GET, - path = "/v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface", + path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface", tags = ["system/networking"], }] async fn networking_switch_port_configuration_link_interface_list( rqctx: RequestContext, - //query_params: Query>, path_params: Path, - // ) -> Result>, HttpError> { -) -> Result, HttpError> { + query_params: Query< + PaginatedById, + >, +) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().name_or_id; + let query = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; @@ -4099,6 +4126,325 @@ async fn networking_switch_port_configuration_link_interface_list( .await } +/// Create interface configuration for a provided switch port link configuration +#[endpoint { + method = POST, + path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_create( + rqctx: RequestContext, + path_params: Path, + new_settings: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("create interface") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// View interface configuration for a provided switch port link configuration +#[endpoint { + method = GET, + path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_view( + rqctx: RequestContext, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("delete interface") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Delete interface configuration for a provided switch port link configuration +#[endpoint { + method = DELETE, + path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_delete( + rqctx: RequestContext, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("delete interface") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// List addresses assigned to a provided interface configuration +#[endpoint { + method = GET, + path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_address_list( + rqctx: RequestContext, + path_params: Path, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("list interface addresses") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Add address to an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/add", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_address_add( + rqctx: RequestContext, + path_params: Path, + address: TypedBody, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("add interface address") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Remove address from an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/remove", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_address_remove( + rqctx: RequestContext, + path_params: Path, + address: TypedBody, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("remove interface address") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// List routes assigned to a provided interface configuration +#[endpoint { + method = GET, + path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_route_list( + rqctx: RequestContext, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("list interface routes") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Add route to an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/add", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_route_add( + rqctx: RequestContext, + path_params: Path, + route: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("add interface route") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Remove address from an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/remove", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_route_remove( + rqctx: RequestContext, + path_params: Path, + route: TypedBody, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("remove interface route") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// List bgp peers assigned to a provided interface configuration +#[endpoint { + method = GET, + path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_bgp_peer_list( + rqctx: RequestContext, + path_params: Path, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("list interface routes") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Add bgp peer to an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/add", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_bgp_peer_add( + rqctx: RequestContext, + path_params: Path, + bgp_peer: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("add interface route") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Remove bgp peer from an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/remove", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_link_interface_bgp_peer_remove( + rqctx: RequestContext, + path_params: Path, + bgp_peer: TypedBody, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let query = path_params.into_inner().configuration; + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + todo!("remove interface route") + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + /// List switch ports #[endpoint { method = GET, diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index d32ac505dc1..17aa89e3117 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -195,8 +195,27 @@ networking_loopback_address_delete DELETE /v1/system/networking/loopback networking_loopback_address_list GET /v1/system/networking/loopback-address networking_switch_port_configuration_create POST /v1/system/networking/switch-port-configuration networking_switch_port_configuration_delete DELETE /v1/system/networking/switch-port-configuration +networking_switch_port_configuration_geometry_set POST /v1/system/networking/switch-port-configuration/{configuration}/geometry +networking_switch_port_configuration_geometry_view GET /v1/system/networking/switch-port-configuration/{configuration}/geometry +networking_switch_port_configuration_link_create POST /v1/system/networking/switch-port-configuration/{configuration}/link +networking_switch_port_configuration_link_delete DELETE /v1/system/networking/switch-port-configuration/{configuration}/link/{link} +networking_switch_port_configuration_link_interface_address_add POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/add +networking_switch_port_configuration_link_interface_address_list GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address +networking_switch_port_configuration_link_interface_address_remove POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/remove +networking_switch_port_configuration_link_interface_bgp_peer_add POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/add +networking_switch_port_configuration_link_interface_bgp_peer_list GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer +networking_switch_port_configuration_link_interface_bgp_peer_remove POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/remove +networking_switch_port_configuration_link_interface_create POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface +networking_switch_port_configuration_link_interface_delete DELETE /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface} +networking_switch_port_configuration_link_interface_list GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface +networking_switch_port_configuration_link_interface_route_add POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/add +networking_switch_port_configuration_link_interface_route_list GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route +networking_switch_port_configuration_link_interface_route_remove POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/remove +networking_switch_port_configuration_link_interface_view GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface} +networking_switch_port_configuration_link_list GET /v1/system/networking/switch-port-configuration/{configuration}/link +networking_switch_port_configuration_link_view GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link} networking_switch_port_configuration_list GET /v1/system/networking/switch-port-configuration -networking_switch_port_configuration_view GET /v1/system/networking/switch-port-configuration/{name_or_id} +networking_switch_port_configuration_view GET /v1/system/networking/switch-port-configuration/{configuration} API operations found with tag "system/silos" OPERATION ID METHOD URL PATH diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index c5091c5a3bc..91b0b4eb37d 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -1,9 +1,19 @@ API endpoints with no coverage in authz tests: probe_delete (delete "/experimental/v1/probes/{probe}") +networking_switch_port_configuration_link_delete (delete "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}") +networking_switch_port_configuration_link_interface_delete (delete "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}") probe_list (get "/experimental/v1/probes") probe_view (get "/experimental/v1/probes/{probe}") ping (get "/v1/ping") networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status") +networking_switch_port_configuration_geometry_view (get "/v1/system/networking/switch-port-configuration/{configuration}/geometry") +networking_switch_port_configuration_link_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link") +networking_switch_port_configuration_link_view (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}") +networking_switch_port_configuration_link_interface_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface") +networking_switch_port_configuration_link_interface_view (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}") +networking_switch_port_configuration_link_interface_address_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address") +networking_switch_port_configuration_link_interface_bgp_peer_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer") +networking_switch_port_configuration_link_interface_route_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") device_access_token (post "/device/token") @@ -11,3 +21,12 @@ probe_create (post "/experimental/v1/probes") login_saml (post "/login/{silo_name}/saml/{provider_name}") login_local (post "/v1/login/{silo_name}/local") logout (post "/v1/logout") +networking_switch_port_configuration_geometry_set (post "/v1/system/networking/switch-port-configuration/{configuration}/geometry") +networking_switch_port_configuration_link_create (post "/v1/system/networking/switch-port-configuration/{configuration}/link") +networking_switch_port_configuration_link_interface_create (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface") +networking_switch_port_configuration_link_interface_address_add (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/add") +networking_switch_port_configuration_link_interface_address_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/remove") +networking_switch_port_configuration_link_interface_bgp_peer_add (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/add") +networking_switch_port_configuration_link_interface_bgp_peer_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/remove") +networking_switch_port_configuration_link_interface_route_add (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/add") +networking_switch_port_configuration_link_interface_route_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/remove") diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 434ac38f0bb..ef9ba6be3ff 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1780,25 +1780,38 @@ pub struct Address { /// Select a port settings object by an optional name or id. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct SwitchPortSettingsSelector { - /// An optional name or id to use when selecting port settings. - pub port_settings: Option, + /// An optional name or id to use when selecting a switch port configuration. + pub configuration: Option, } /// Select a port settings info object by name or id. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct SwitchPortSettingsInfoSelector { - /// A name or id to use when selecting switch port settings info objects. - pub name_or_id: NameOrId, + /// A name or id to use when selecting a switch port configuration. + pub configuration: NameOrId, } -/// Select a link settings i nfo object by port settings name and link name. +/// Select a link settings info object by port settings name and link name. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct SwitchPortSettingsLinkInfoSelector { - /// A name or id to use when selecting switch port settings info objects. - pub name_or_id: NameOrId, + /// A name or id to use when selecting a switch port configuration. + pub configuration: NameOrId, + + /// Link name + pub link: Name, +} + +/// Select an interface settings info object by port settings name, link name, and interface name. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortSettingsLinkInterfaceInfoSelector { + /// A name or id to use when selecting a switch port configuration. + pub configuration: NameOrId, /// Link name pub link: Name, + + /// Interface name + pub interface: Name, } /// Select a switch port by name. @@ -1825,6 +1838,13 @@ pub struct SwitchPortPageSelector { pub switch_port_id: Option, } +/// Select switch port interface config by id +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortInterfaceConfigPageSelector { + /// An optional switch port id to use when listing switch ports. + pub switch_port_id: Option, +} + /// Parameters for applying settings to switch ports. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct SwitchPortApplySettings { diff --git a/openapi/nexus.json b/openapi/nexus.json index 77cc787e05a..dc7c65d1824 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7056,14 +7056,22 @@ } } }, - "/v1/system/networking/switch-port-settings": { + "/v1/system/networking/switch-port-configuration": { "get": { "tags": [ "system/networking" ], "summary": "List switch port settings", - "operationId": "networking_switch_port_settings_list", + "operationId": "networking_switch_port_configuration_list", "parameters": [ + { + "in": "query", + "name": "configuration", + "description": "An optional name or id to use when selecting a switch port configuration.", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, { "in": "query", "name": "limit", @@ -7076,27 +7084,880 @@ } }, { - "in": "query", - "name": "page_token", - "description": "Token returned by previous call to retrieve the subsequent page", + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettingsResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "tags": [ + "system/networking" + ], + "summary": "Create switch port settings", + "operationId": "networking_switch_port_configuration_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettingsCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettingsView" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "system/networking" + ], + "summary": "Delete switch port settings", + "operationId": "networking_switch_port_configuration_delete", + "parameters": [ + { + "in": "query", + "name": "configuration", + "description": "An optional name or id to use when selecting a switch port configuration.", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "Get information about a named set of switch-port-settings", + "operationId": "networking_switch_port_configuration_view", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettingsView" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/geometry": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "Get switch port geometry for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_geometry_view", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortGeometry2" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "tags": [ + "system/networking" + ], + "summary": "Set switch port geometry for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_geometry_set", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortGeometry" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortGeometry2" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "List links for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_list", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_SwitchPortLinkConfig", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortLinkConfig" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "tags": [ + "system/networking" + ], + "summary": "Create a link for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_create", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkConfigCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortLinkConfig" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "View a link for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_view", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortLinkConfig" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "system/networking" + ], + "summary": "Delete a link for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_delete", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "List interfaces for a provided switch port link configuration", + "operationId": "networking_switch_port_configuration_link_interface_list", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/IdSortMode" + } + }, + { + "in": "query", + "name": "switch_port_id", + "description": "An optional switch port id to use when listing switch ports.", + "schema": { + "nullable": true, + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchInterfaceConfigResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "tags": [ + "system/networking" + ], + "summary": "Create interface configuration for a provided switch port link configuration", + "operationId": "networking_switch_port_configuration_link_interface_create", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchInterfaceConfigCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchInterfaceConfig" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "View interface configuration for a provided switch port link configuration", + "operationId": "networking_switch_port_configuration_link_interface_view", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchInterfaceConfig" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "system/networking" + ], + "summary": "Delete interface configuration for a provided switch port link configuration", + "operationId": "networking_switch_port_configuration_link_interface_delete", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchInterfaceConfig" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "List addresses assigned to a provided interface configuration", + "operationId": "networking_switch_port_configuration_link_interface_address_list", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_SwitchInterfaceConfig", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchInterfaceConfig" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/add": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Add address to an interface configuration", + "operationId": "networking_switch_port_configuration_link_interface_address_add", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressConfig" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchInterfaceConfigResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/remove": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Remove address from an interface configuration", + "operationId": "networking_switch_port_configuration_link_interface_address_remove", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressConfig" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchInterfaceConfigResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "List bgp peers assigned to a provided interface configuration", + "operationId": "networking_switch_port_configuration_link_interface_bgp_peer_list", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, "schema": { - "nullable": true, - "type": "string" + "$ref": "#/components/schemas/NameOrId" } }, { - "in": "query", - "name": "port_settings", - "description": "An optional name or id to use when selecting port settings.", + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, "schema": { - "$ref": "#/components/schemas/NameOrId" + "$ref": "#/components/schemas/Name" } }, { - "in": "query", - "name": "sort_by", + "in": "path", + "name": "link", + "description": "Link name", + "required": true, "schema": { - "$ref": "#/components/schemas/NameOrIdSortMode" + "$ref": "#/components/schemas/Name" } } ], @@ -7106,7 +7967,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsResultsPage" + "$ref": "#/components/schemas/BgpPeerConfig" } } } @@ -7117,22 +7978,50 @@ "5XX": { "$ref": "#/components/responses/Error" } - }, - "x-dropshot-pagination": { - "required": [] } - }, + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/add": { "post": { "tags": [ "system/networking" ], - "summary": "Create switch port settings", - "operationId": "networking_switch_port_settings_create", + "summary": "Add bgp peer to an interface configuration", + "operationId": "networking_switch_port_configuration_link_interface_bgp_peer_add", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsCreate" + "$ref": "#/components/schemas/BgpPeer" } } }, @@ -7144,7 +8033,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsView" + "$ref": "#/components/schemas/BgpPeer" } } } @@ -7156,23 +8045,54 @@ "$ref": "#/components/responses/Error" } } - }, - "delete": { + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/remove": { + "post": { "tags": [ "system/networking" ], - "summary": "Delete switch port settings", - "operationId": "networking_switch_port_settings_delete", + "summary": "Remove bgp peer from an interface configuration", + "operationId": "networking_switch_port_configuration_link_interface_bgp_peer_remove", "parameters": [ { - "in": "query", - "name": "port_settings", - "description": "An optional name or id to use when selecting port settings.", + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpPeer" + } + } + }, + "required": true + }, "responses": { "204": { "description": "successful deletion" @@ -7186,22 +8106,40 @@ } } }, - "/v1/system/networking/switch-port-settings/{port}": { + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route": { "get": { "tags": [ "system/networking" ], - "summary": "Get information about switch port", - "operationId": "networking_switch_port_settings_view", + "summary": "List routes assigned to a provided interface configuration", + "operationId": "networking_switch_port_configuration_link_interface_route_list", "parameters": [ { "in": "path", - "name": "port", - "description": "A name or id to use when selecting switch port settings info objects.", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } } ], "responses": { @@ -7210,7 +8148,73 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsView" + "$ref": "#/components/schemas/RouteConfig" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/add": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Add route to an interface configuration", + "operationId": "networking_switch_port_configuration_link_interface_route_add", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Route" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Route" } } } @@ -7224,6 +8228,65 @@ } } }, + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/remove": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Remove address from an interface configuration", + "operationId": "networking_switch_port_configuration_link_interface_route_remove", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "link", + "description": "Link name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Route" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/system/policy": { "get": { "tags": [ @@ -18978,6 +20041,27 @@ "v6_enabled" ] }, + "SwitchInterfaceConfigResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchInterfaceConfig" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, "SwitchInterfaceKind": { "description": "Indicates the kind for a switch interface.", "oneOf": [ From 78033b5a425c39ecb6d1717f8457cde8bb13b382 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Wed, 31 Jul 2024 19:34:10 +0000 Subject: [PATCH 07/37] plumb through switch port geometry --- nexus/db-model/src/schema.rs | 5 + nexus/db-model/src/schema_versions.rs | 3 +- .../src/db/datastore/switch_port.rs | 111 ++++++++++++++++++ nexus/src/app/switch_port.rs | 24 ++++ nexus/src/external_api/http_entrypoints.rs | 22 ++-- openapi/nexus.json | 2 +- schema/crdb/dbinit.sql | 6 +- 7 files changed, 163 insertions(+), 10 deletions(-) diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index 89ae6c18c5e..dac00a9eb0f 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -1871,3 +1871,8 @@ joinable!(instance_ssh_key -> instance (instance_id)); allow_tables_to_appear_in_same_query!(sled, sled_instance); joinable!(network_interface -> probe (parent_id)); + +allow_tables_to_appear_in_same_query!( + switch_port_settings, + switch_port_settings_port_config +); diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index 3e740590c56..67a07c3fa97 100644 --- a/nexus/db-model/src/schema_versions.rs +++ b/nexus/db-model/src/schema_versions.rs @@ -17,7 +17,7 @@ use std::collections::BTreeMap; /// /// This must be updated when you change the database schema. Refer to /// schema/crdb/README.adoc in the root of this repository for details. -pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(82, 0, 0); +pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(83, 0, 0); /// List of all past database schema versions, in *reverse* order /// @@ -29,6 +29,7 @@ static KNOWN_VERSIONS: Lazy> = Lazy::new(|| { // | leaving the first copy as an example for the next person. // v // KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"), + KnownVersion::new(83, "refactor-network-apis"), KnownVersion::new(82, "region-port"), KnownVersion::new(81, "add-nullable-filesystem-pool"), KnownVersion::new(80, "add-instance-id-to-migrations"), diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 315452c910f..7484a509efc 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -33,6 +33,7 @@ use ipnetwork::IpNetwork; use nexus_db_model::{ SqlU16, SqlU32, SqlU8, SwitchPortBgpPeerConfigAllowExport, SwitchPortBgpPeerConfigAllowImport, SwitchPortBgpPeerConfigCommunity, + SwitchPortGeometry, }; use nexus_types::external_api::params; use omicron_common::api::external::http_pagination::PaginatedBy; @@ -627,6 +628,116 @@ impl DataStore { }) } + pub async fn switch_port_configuration_geometry_get( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + ) -> LookupResult { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings::dsl as port_settings_dsl; + use db::schema::switch_port_settings_port_config as config; + use db::schema::switch_port_settings_port_config::dsl; + + let query = match name_or_id { + NameOrId::Id(id) => { + // find port config using port settings id + port_settings_dsl::switch_port_settings + .inner_join(dsl::switch_port_settings_port_config.on( + dsl::port_settings_id.eq(port_settings_dsl::id), + )) + .filter(port_settings::id.eq(id)) + .into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + port_settings_dsl::switch_port_settings + .inner_join(dsl::switch_port_settings_port_config.on( + dsl::port_settings_id.eq(port_settings_dsl::id), + )) + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + let geometry: SwitchPortGeometry = query + .select(config::geometry) + .limit(1) + .first_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(geometry) + } + + pub async fn switch_port_configuration_geometry_set( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + new_geometry: SwitchPortGeometry, + ) -> CreateResult { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings::dsl as port_settings_dsl; + use db::schema::switch_port_settings_port_config::dsl; + + let conn = self.pool_connection_authorized(opctx).await?; + + self.transaction_retry_wrapper("switch_port_configuration_geometry_set") + .transaction(&conn, |conn| { + let identity = name_or_id.clone(); + async move { + // we query for the parent record instead of trusting that the + // uuid is valid, since we don't have true referential integrity between + // the tables + let table = + port_settings_dsl::switch_port_settings.inner_join( + dsl::switch_port_settings_port_config + .on(dsl::port_settings_id + .eq(port_settings_dsl::id)), + ); + + let query = match identity { + NameOrId::Id(id) => { + // find port config using port settings id + table.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + table + .filter( + port_settings::name.eq(name.to_string()), + ) + .into_boxed() + } + }; + + // get settings id + let port_settings_id: Uuid = query + .select(port_settings_dsl::id) + .limit(1) + .first_async(&conn) + .await?; + + let port_config = SwitchPortConfig { + port_settings_id, + geometry: new_geometry, + }; + + // create or update geometry + diesel::insert_into(dsl::switch_port_settings_port_config) + .values(port_config) + .on_conflict(dsl::port_settings_id) + .do_update() + .set(dsl::geometry.eq(new_geometry)) + .execute_async(&conn) + .await?; + + Ok(new_geometry) + } + }) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + } + // switch ports pub async fn switch_port_create( diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 9726a59d331..b990b66c291 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -8,6 +8,7 @@ use db::datastore::SwitchPortSettingsCombinedResult; use dpd_client::types::LinkId; use dpd_client::types::PortId; use http::StatusCode; +use nexus_db_model::SwitchPortGeometry; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; @@ -127,6 +128,29 @@ impl super::Nexus { self.db_datastore.switch_port_settings_get(opctx, name_or_id).await } + pub(crate) async fn switch_port_configuration_geometry_get( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + ) -> LookupResult { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_geometry_get(opctx, name_or_id) + .await + } + + pub(crate) async fn switch_port_configuration_geometry_set( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + geometry: SwitchPortGeometry, + ) -> LookupResult { + opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_geometry_set(opctx, name_or_id, geometry) + .await + } + async fn switch_port_create( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 2aaa6fecc44..2ef6cdd23ac 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -3951,11 +3951,13 @@ async fn networking_switch_port_configuration_geometry_view( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let config = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("fetch geometry") + let geometry = nexus + .switch_port_configuration_geometry_get(&opctx, config) + .await?; + Ok(HttpResponseOk(geometry.into())) }; apictx .context @@ -3973,16 +3975,22 @@ async fn networking_switch_port_configuration_geometry_view( async fn networking_switch_port_configuration_geometry_set( rqctx: RequestContext, path_params: Path, - new_settings: TypedBody, + new_settings: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let config = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("set geometry") + let geometry = nexus + .switch_port_configuration_geometry_set( + &opctx, + config, + new_settings.into_inner().geometry.into(), + ) + .await?; + Ok(HttpResponseCreated(geometry.into())) }; apictx .context diff --git a/openapi/nexus.json b/openapi/nexus.json index dc7c65d1824..7103622bf63 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7282,7 +7282,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortGeometry" + "$ref": "#/components/schemas/SwitchPortConfigCreate" } } }, diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index 7d93a5d5bda..befccea4f7b 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -4132,6 +4132,10 @@ CREATE INDEX IF NOT EXISTS lookup_region_snapshot_by_snapshot_id on omicron.publ snapshot_id ); + +/* Lookup switch port settings by name */ +CREATE INDEX IF NOT EXISTS switch_port_settings_name ON omicron.public.switch_port_settings (name); + /* * Keep this at the end of file so that the database does not contain a version * until it is fully populated. @@ -4143,7 +4147,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - (TRUE, NOW(), NOW(), '82.0.0', NULL) + (TRUE, NOW(), NOW(), '83.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; From d8c2f42f098bc735dcbe48be7581912eebeebe40 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Wed, 31 Jul 2024 19:34:25 +0000 Subject: [PATCH 08/37] fixup! plumb through switch port geometry --- schema/crdb/refactor-network-apis/up01.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 schema/crdb/refactor-network-apis/up01.sql diff --git a/schema/crdb/refactor-network-apis/up01.sql b/schema/crdb/refactor-network-apis/up01.sql new file mode 100644 index 00000000000..5e645ed8b07 --- /dev/null +++ b/schema/crdb/refactor-network-apis/up01.sql @@ -0,0 +1 @@ +CREATE INDEX IF NOT EXISTS switch_port_settings_name ON omicron.public.switch_port_settings (name); From bca07feb8fa6677fb42b578b17d8f6344d8d9ce9 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 1 Aug 2024 18:16:07 +0000 Subject: [PATCH 09/37] list links --- nexus/db-model/src/schema.rs | 3 +- .../src/db/datastore/switch_port.rs | 65 ++++++++++++++----- nexus/src/app/switch_port.rs | 12 ++++ nexus/src/external_api/http_entrypoints.rs | 7 +- 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index dac00a9eb0f..da25eeb1b8f 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -1874,5 +1874,6 @@ joinable!(network_interface -> probe (parent_id)); allow_tables_to_appear_in_same_query!( switch_port_settings, - switch_port_settings_port_config + switch_port_settings_port_config, + switch_port_settings_link_config, ); diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 7484a509efc..fe9dae4c35b 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -638,24 +638,21 @@ impl DataStore { use db::schema::switch_port_settings_port_config as config; use db::schema::switch_port_settings_port_config::dsl; + let dataset = port_settings_dsl::switch_port_settings.inner_join( + dsl::switch_port_settings_port_config + .on(dsl::port_settings_id.eq(port_settings_dsl::id)), + ); + let query = match name_or_id { NameOrId::Id(id) => { // find port config using port settings id - port_settings_dsl::switch_port_settings - .inner_join(dsl::switch_port_settings_port_config.on( - dsl::port_settings_id.eq(port_settings_dsl::id), - )) - .filter(port_settings::id.eq(id)) - .into_boxed() + dataset.filter(port_settings::id.eq(id)).into_boxed() } NameOrId::Name(name) => { // find port config using port settings name - port_settings_dsl::switch_port_settings - .inner_join(dsl::switch_port_settings_port_config.on( - dsl::port_settings_id.eq(port_settings_dsl::id), - )) - .filter(port_settings::name.eq(name.to_string())) - .into_boxed() + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() } }; @@ -688,7 +685,7 @@ impl DataStore { // we query for the parent record instead of trusting that the // uuid is valid, since we don't have true referential integrity between // the tables - let table = + let dataset = port_settings_dsl::switch_port_settings.inner_join( dsl::switch_port_settings_port_config .on(dsl::port_settings_id @@ -698,11 +695,13 @@ impl DataStore { let query = match identity { NameOrId::Id(id) => { // find port config using port settings id - table.filter(port_settings::id.eq(id)).into_boxed() + dataset + .filter(port_settings::id.eq(id)) + .into_boxed() } NameOrId::Name(name) => { // find port config using port settings name - table + dataset .filter( port_settings::name.eq(name.to_string()), ) @@ -738,6 +737,42 @@ impl DataStore { .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) } + pub async fn switch_port_configuration_link_list( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + ) -> ListResultVec { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings::dsl as port_settings_dsl; + use db::schema::switch_port_settings_link_config::dsl as link_dsl; + + let dataset = port_settings_dsl::switch_port_settings.inner_join( + link_dsl::switch_port_settings_link_config + .on(link_dsl::port_settings_id.eq(port_settings_dsl::id)), + ); + + let query = match name_or_id { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + let configs: Vec = query + .select(SwitchPortLinkConfig::as_select()) + .load_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(configs) + } + // switch ports pub async fn switch_port_create( diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index b990b66c291..47d75b5721e 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -9,6 +9,7 @@ use dpd_client::types::LinkId; use dpd_client::types::PortId; use http::StatusCode; use nexus_db_model::SwitchPortGeometry; +use nexus_db_model::SwitchPortLinkConfig; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; @@ -151,6 +152,17 @@ impl super::Nexus { .await } + pub(crate) async fn switch_port_configuration_link_list( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + ) -> ListResultVec { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_link_list(opctx, name_or_id) + .await + } + async fn switch_port_create( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 2ef6cdd23ac..f60760100da 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -4013,11 +4013,12 @@ async fn networking_switch_port_configuration_link_list( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let config = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("list links") + let settings = + nexus.switch_port_configuration_link_list(&opctx, config).await?; + Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) }; apictx .context From 30c217c2e802ea255955449e547c48d584fcaff0 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 1 Aug 2024 21:59:34 +0000 Subject: [PATCH 10/37] return structured data for geometry --- nexus/db-queries/src/db/datastore/switch_port.rs | 13 ++++++------- nexus/src/app/switch_port.rs | 5 +++-- nexus/src/external_api/http_entrypoints.rs | 9 +++++---- openapi/nexus.json | 4 ++-- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index fe9dae4c35b..21117097620 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -632,10 +632,9 @@ impl DataStore { &self, opctx: &OpContext, name_or_id: NameOrId, - ) -> LookupResult { + ) -> LookupResult { use db::schema::switch_port_settings as port_settings; use db::schema::switch_port_settings::dsl as port_settings_dsl; - use db::schema::switch_port_settings_port_config as config; use db::schema::switch_port_settings_port_config::dsl; let dataset = port_settings_dsl::switch_port_settings.inner_join( @@ -656,8 +655,8 @@ impl DataStore { } }; - let geometry: SwitchPortGeometry = query - .select(config::geometry) + let geometry: SwitchPortConfig = query + .select(SwitchPortConfig::as_select()) .limit(1) .first_async(&*self.pool_connection_authorized(opctx).await?) .await @@ -671,7 +670,7 @@ impl DataStore { opctx: &OpContext, name_or_id: NameOrId, new_geometry: SwitchPortGeometry, - ) -> CreateResult { + ) -> CreateResult { use db::schema::switch_port_settings as port_settings; use db::schema::switch_port_settings::dsl as port_settings_dsl; use db::schema::switch_port_settings_port_config::dsl; @@ -723,14 +722,14 @@ impl DataStore { // create or update geometry diesel::insert_into(dsl::switch_port_settings_port_config) - .values(port_config) + .values(port_config.clone()) .on_conflict(dsl::port_settings_id) .do_update() .set(dsl::geometry.eq(new_geometry)) .execute_async(&conn) .await?; - Ok(new_geometry) + Ok(port_config) } }) .await diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 47d75b5721e..becbad757e6 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -8,6 +8,7 @@ use db::datastore::SwitchPortSettingsCombinedResult; use dpd_client::types::LinkId; use dpd_client::types::PortId; use http::StatusCode; +use nexus_db_model::SwitchPortConfig; use nexus_db_model::SwitchPortGeometry; use nexus_db_model::SwitchPortLinkConfig; use nexus_db_queries::authz; @@ -133,7 +134,7 @@ impl super::Nexus { &self, opctx: &OpContext, name_or_id: NameOrId, - ) -> LookupResult { + ) -> LookupResult { opctx.authorize(authz::Action::Read, &authz::FLEET).await?; self.db_datastore .switch_port_configuration_geometry_get(opctx, name_or_id) @@ -145,7 +146,7 @@ impl super::Nexus { opctx: &OpContext, name_or_id: NameOrId, geometry: SwitchPortGeometry, - ) -> LookupResult { + ) -> CreateResult { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; self.db_datastore .switch_port_configuration_geometry_set(opctx, name_or_id, geometry) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index f60760100da..ddcfc77a08e 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -44,7 +44,6 @@ use nexus_types::external_api::{ params::{BgpPeerConfig, RouteConfig}, shared::{BfdStatus, ProbeInfo}, }; -use omicron_common::api::external::http_pagination::marker_for_name; use omicron_common::api::external::http_pagination::PaginatedById; use omicron_common::api::external::http_pagination::PaginatedByName; use omicron_common::api::external::http_pagination::PaginatedByNameOrId; @@ -72,7 +71,6 @@ use omicron_common::api::external::Probe; use omicron_common::api::external::RouterRoute; use omicron_common::api::external::RouterRouteKind; use omicron_common::api::external::SwitchPort; -use omicron_common::api::external::SwitchPortGeometry; use omicron_common::api::external::SwitchPortSettings; use omicron_common::api::external::SwitchPortSettingsView; use omicron_common::api::external::TufRepoGetResponse; @@ -82,6 +80,9 @@ use omicron_common::api::external::VpcFirewallRules; use omicron_common::api::external::{ http_pagination::data_page_params_for, AggregateBgpMessageHistory, }; +use omicron_common::api::external::{ + http_pagination::marker_for_name, SwitchPortConfig, +}; use omicron_common::api::external::{ http_pagination::marker_for_name_or_id, SwitchPortLinkConfig, }; @@ -3947,7 +3948,7 @@ async fn networking_switch_port_configuration_view( async fn networking_switch_port_configuration_geometry_view( rqctx: RequestContext, path_params: Path, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -3976,7 +3977,7 @@ async fn networking_switch_port_configuration_geometry_set( rqctx: RequestContext, path_params: Path, new_settings: TypedBody, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; diff --git a/openapi/nexus.json b/openapi/nexus.json index 7103622bf63..99b6fecdbc5 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7248,7 +7248,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortGeometry2" + "$ref": "#/components/schemas/SwitchPortConfig" } } } @@ -7294,7 +7294,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortGeometry2" + "$ref": "#/components/schemas/SwitchPortConfig" } } } From 700090465cd408b1c3fdf9a2d0ac3259e63a2174 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Tue, 6 Aug 2024 16:07:26 +0000 Subject: [PATCH 11/37] plumb through switch port link configuration --- .../src/db/datastore/switch_port.rs | 136 +++++++++++++----- nexus/src/app/switch_port.rs | 28 ++++ nexus/src/external_api/http_entrypoints.rs | 22 +-- 3 files changed, 146 insertions(+), 40 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 21117097620..117353f4e4e 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -671,8 +671,6 @@ impl DataStore { name_or_id: NameOrId, new_geometry: SwitchPortGeometry, ) -> CreateResult { - use db::schema::switch_port_settings as port_settings; - use db::schema::switch_port_settings::dsl as port_settings_dsl; use db::schema::switch_port_settings_port_config::dsl; let conn = self.pool_connection_authorized(opctx).await?; @@ -684,36 +682,8 @@ impl DataStore { // we query for the parent record instead of trusting that the // uuid is valid, since we don't have true referential integrity between // the tables - let dataset = - port_settings_dsl::switch_port_settings.inner_join( - dsl::switch_port_settings_port_config - .on(dsl::port_settings_id - .eq(port_settings_dsl::id)), - ); - - let query = match identity { - NameOrId::Id(id) => { - // find port config using port settings id - dataset - .filter(port_settings::id.eq(id)) - .into_boxed() - } - NameOrId::Name(name) => { - // find port config using port settings name - dataset - .filter( - port_settings::name.eq(name.to_string()), - ) - .into_boxed() - } - }; - - // get settings id - let port_settings_id: Uuid = query - .select(port_settings_dsl::id) - .limit(1) - .first_async(&conn) - .await?; + let port_settings_id = + switch_port_configuration_id(&conn, identity).await?; let port_config = SwitchPortConfig { port_settings_id, @@ -772,6 +742,82 @@ impl DataStore { Ok(configs) } + pub async fn switch_port_configuration_link_view( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + link: Name, + ) -> LookupResult { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings::dsl as port_settings_dsl; + use db::schema::switch_port_settings_link_config::dsl as link_dsl; + + let dataset = port_settings_dsl::switch_port_settings.inner_join( + link_dsl::switch_port_settings_link_config + .on(link_dsl::port_settings_id.eq(port_settings_dsl::id)), + ); + + let query = match name_or_id { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + let config: SwitchPortLinkConfig = query + .filter(link_dsl::link_name.eq(link.to_string())) + .select(SwitchPortLinkConfig::as_select()) + .limit(1) + .first_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(config) + } + + pub async fn switch_port_configuration_link_delete( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + link: Name, + ) -> DeleteResult { + use db::schema::switch_port_settings_link_config::dsl as link_dsl; + + let conn = self.pool_connection_authorized(opctx).await?; + + self.transaction_retry_wrapper( + "switch_port_configuration_geometry_set", + ) + .transaction(&conn, |conn| { + let identity = name_or_id.clone(); + let link_name = link.clone(); + + async move { + // fetch id of parent record + let parent_id = + switch_port_configuration_id(&conn, identity).await?; + + // delete child record + diesel::delete(link_dsl::switch_port_settings_link_config) + .filter(link_dsl::port_settings_id.eq(parent_id)) + .filter(link_dsl::link_name.eq(link_name.to_string())) + .execute_async(&conn) + .await?; + + Ok(()) + } + }) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + Ok(()) + } + // switch ports pub async fn switch_port_create( @@ -1644,6 +1690,32 @@ async fn do_switch_port_settings_delete( Ok(()) } +async fn switch_port_configuration_id( + conn: &async_bb8_diesel::Connection>, + name_or_id: NameOrId, +) -> diesel::result::QueryResult { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings::dsl as port_settings_dsl; + + let dataset = port_settings_dsl::switch_port_settings; + + let query = match name_or_id { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + // get settings id + query.select(port_settings_dsl::id).limit(1).first_async(conn).await +} + #[cfg(test)] mod test { use crate::db::datastore::test_utils::datastore_test; diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index becbad757e6..2a80025dc57 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -164,6 +164,34 @@ impl super::Nexus { .await } + pub(crate) async fn switch_port_configuration_link_view( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + link: Name, + ) -> LookupResult { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_link_view(opctx, name_or_id, link.into()) + .await + } + + pub(crate) async fn switch_port_configuration_link_delete( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + link: Name, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_link_delete( + opctx, + name_or_id, + link.into(), + ) + .await + } + async fn switch_port_create( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index ddcfc77a08e..83fc59715f5 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -4042,10 +4042,10 @@ async fn networking_switch_port_configuration_link_create( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let config = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + let settings = nexus.switch_port_settings_get(&opctx, &config).await?; todo!("create link") }; apictx @@ -4068,11 +4068,14 @@ async fn networking_switch_port_configuration_link_view( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let params::SwitchPortSettingsLinkInfoSelector { configuration, link } = + path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("view link") + let settings = nexus + .switch_port_configuration_link_view(&opctx, configuration, link) + .await?; + Ok(HttpResponseOk(settings.into())) }; apictx .context @@ -4094,11 +4097,14 @@ async fn networking_switch_port_configuration_link_delete( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let params::SwitchPortSettingsLinkInfoSelector { configuration, link } = + path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("delete link") + nexus + .switch_port_configuration_link_delete(&opctx, configuration, link) + .await?; + Ok(HttpResponseDeleted {}) }; apictx .context From 1c695d0e739a4dbd745a374a010b26ed80f8640b Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Tue, 6 Aug 2024 20:15:44 +0000 Subject: [PATCH 12/37] WIP: plumb switch port link create --- .../src/db/datastore/switch_port.rs | 156 +++++++++++++++--- nexus/src/app/switch_port.rs | 16 ++ nexus/src/external_api/http_entrypoints.rs | 12 +- nexus/types/src/external_api/params.rs | 22 +++ 4 files changed, 178 insertions(+), 28 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 117353f4e4e..ed7d7b89227 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -25,8 +25,8 @@ use crate::db::pagination::paginated; use crate::transaction_retry::OptionalError; use async_bb8_diesel::{AsyncRunQueryDsl, Connection}; use diesel::{ - CombineDsl, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, - PgConnection, QueryDsl, SelectableHelper, + CombineDsl, ExpressionMethods, Insertable, JoinOnDsl, + NullableExpressionMethods, PgConnection, QueryDsl, SelectableHelper, }; use diesel_dtrace::DTraceConnection; use ipnetwork::IpNetwork; @@ -742,6 +742,87 @@ impl DataStore { Ok(configs) } + pub async fn switch_port_configuration_link_create( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + new_settings: params::NamedLinkConfigCreate, + ) -> CreateResult { + use db::schema::lldp_service_config::dsl as lldp_service_dsl; + use db::schema::switch_port_settings_link_config::dsl as link_dsl; + + #[derive(Clone, Debug)] + enum Lldp { + Enabled { lldp_config: NameOrId }, + Disabled, + } + + let conn = self.pool_connection_authorized(opctx).await?; + let lldp = + match (new_settings.lldp.enabled, new_settings.lldp.lldp_config) { + (true, Some(name_or_id)) => { + Ok(Lldp::Enabled { lldp_config: name_or_id }) + } + (true, None) => Err(Error::conflict( + "cannot enable lldp without providing configuration id", + )), + (false, _) => Ok(Lldp::Disabled), + }?; + + let config = self + .transaction_retry_wrapper("switch_port_configuration_link_create") + .transaction(&conn, |conn| { + let identity = name_or_id.clone(); + let lldp = lldp.clone(); + + async move { + // fetch id of parent record + let parent_id = + switch_port_configuration_id(&conn, identity).await?; + + let lldp_service_config = match lldp { + Lldp::Enabled { lldp_config } => { + let config_id = + lldp_configuration_id(&conn, name_or_id) + .await?; + Ok(LldpServiceConfig::new(true, Some(config_id))) + } + Lldp::Disabled => { + Ok(LldpServiceConfig::new(false, None)) + } + }?; + + diesel::insert_into(lldp_service_dsl::lldp_service_config) + .values(lldp_service_config.clone()) + .execute_async(&conn) + .await?; + + let link_config = SwitchPortLinkConfig { + port_settings_id: parent_id, + lldp_service_config_id: lldp_service_config.id, + link_name: new_settings.name.to_string(), + mtu: new_settings.mtu.into(), + fec: new_settings.fec.into(), + speed: new_settings.speed.into(), + autoneg: new_settings.autoneg, + }; + + let link = diesel::insert_into( + link_dsl::switch_port_settings_link_config, + ) + .values(link_config.clone()) + .returning(SwitchPortLinkConfig::as_returning()) + .get_result_async(&conn) + .await?; + + Ok(link) + } + }) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + Ok(config) + } + pub async fn switch_port_configuration_link_view( &self, opctx: &OpContext, @@ -791,30 +872,31 @@ impl DataStore { let conn = self.pool_connection_authorized(opctx).await?; - self.transaction_retry_wrapper( - "switch_port_configuration_geometry_set", - ) - .transaction(&conn, |conn| { - let identity = name_or_id.clone(); - let link_name = link.clone(); - - async move { - // fetch id of parent record - let parent_id = - switch_port_configuration_id(&conn, identity).await?; - - // delete child record - diesel::delete(link_dsl::switch_port_settings_link_config) - .filter(link_dsl::port_settings_id.eq(parent_id)) - .filter(link_dsl::link_name.eq(link_name.to_string())) - .execute_async(&conn) - .await?; + self.transaction_retry_wrapper("switch_port_configuration_link_delete") + .transaction(&conn, |conn| { + let identity = name_or_id.clone(); + let link_name = link.clone(); - Ok(()) - } - }) - .await - .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + async move { + // fetch id of parent record + let parent_id = + switch_port_configuration_id(&conn, identity).await?; + + // delete lldp service config + todo!("delete lldp service config"); + + // delete child record + diesel::delete(link_dsl::switch_port_settings_link_config) + .filter(link_dsl::port_settings_id.eq(parent_id)) + .filter(link_dsl::link_name.eq(link_name.to_string())) + .execute_async(&conn) + .await?; + + Ok(()) + } + }) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; Ok(()) } @@ -1716,6 +1798,30 @@ async fn switch_port_configuration_id( query.select(port_settings_dsl::id).limit(1).first_async(conn).await } +async fn lldp_configuration_id( + conn: &async_bb8_diesel::Connection>, + name_or_id: NameOrId, +) -> diesel::result::QueryResult { + use db::schema::lldp_config; + use db::schema::lldp_config::dsl as lldp_config_dsl; + + let dataset = lldp_config_dsl::lldp_config; + + let query = match name_or_id { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(lldp_config::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset.filter(lldp_config::name.eq(name.to_string())).into_boxed() + } + }; + + // get settings id + query.select(lldp_config_dsl::id).limit(1).first_async(conn).await +} + #[cfg(test)] mod test { use crate::db::datastore::test_utils::datastore_test; diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 2a80025dc57..05f246c5e02 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -164,6 +164,22 @@ impl super::Nexus { .await } + pub(crate) async fn switch_port_configuration_link_create( + &self, + opctx: &OpContext, + name_or_id: NameOrId, + new_settings: params::NamedLinkConfigCreate, + ) -> CreateResult { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_link_create( + opctx, + name_or_id, + new_settings, + ) + .await + } + pub(crate) async fn switch_port_configuration_link_view( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 83fc59715f5..ca5702f1008 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -4037,7 +4037,7 @@ async fn networking_switch_port_configuration_link_list( async fn networking_switch_port_configuration_link_create( rqctx: RequestContext, path_params: Path, - new_settings: TypedBody, + new_settings: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4045,8 +4045,14 @@ async fn networking_switch_port_configuration_link_create( let config = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &config).await?; - todo!("create link") + let settings = nexus + .switch_port_configuration_link_create( + &opctx, + config, + new_settings.into_inner(), + ) + .await?; + Ok(HttpResponseCreated(settings.into())) }; apictx .context diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index ef9ba6be3ff..16657aa03d4 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1558,6 +1558,28 @@ pub struct LinkConfigCreate { pub autoneg: bool, } +/// Named switch link configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct NamedLinkConfigCreate { + /// Name of link + pub name: Name, + + /// Maximum transmission unit for the link. + pub mtu: u16, + + /// The link-layer discovery protocol (LLDP) configuration for the link. + pub lldp: LldpServiceConfigCreate, + + /// The forward error correction mode of the link. + pub fec: LinkFec, + + /// The speed of the link. + pub speed: LinkSpeed, + + /// Whether or not to set autonegotiation + pub autoneg: bool, +} + /// The LLDP configuration associated with a port. LLDP may be either enabled or /// disabled, if enabled, an LLDP configuration must be provided by name or id. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] From ba507164e38d3f977ef8bb57e7568b11480ff0b0 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 9 Aug 2024 17:12:20 +0000 Subject: [PATCH 13/37] create / delete link config --- .../src/db/datastore/switch_port.rs | 52 ++++++++--------- nexus/types/src/external_api/params.rs | 4 +- openapi/nexus.json | 58 ++++++++++++++++++- 3 files changed, 82 insertions(+), 32 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index ed7d7b89227..a96e3098068 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -751,45 +751,29 @@ impl DataStore { use db::schema::lldp_service_config::dsl as lldp_service_dsl; use db::schema::switch_port_settings_link_config::dsl as link_dsl; - #[derive(Clone, Debug)] - enum Lldp { - Enabled { lldp_config: NameOrId }, - Disabled, - } - let conn = self.pool_connection_authorized(opctx).await?; - let lldp = - match (new_settings.lldp.enabled, new_settings.lldp.lldp_config) { - (true, Some(name_or_id)) => { - Ok(Lldp::Enabled { lldp_config: name_or_id }) - } - (true, None) => Err(Error::conflict( - "cannot enable lldp without providing configuration id", - )), - (false, _) => Ok(Lldp::Disabled), - }?; let config = self .transaction_retry_wrapper("switch_port_configuration_link_create") .transaction(&conn, |conn| { let identity = name_or_id.clone(); - let lldp = lldp.clone(); + let new_settings = new_settings.clone(); async move { // fetch id of parent record let parent_id = switch_port_configuration_id(&conn, identity).await?; - let lldp_service_config = match lldp { - Lldp::Enabled { lldp_config } => { + let lldp_service_config = match new_settings.lldp_config { + Some(name_or_id) => { let config_id = lldp_configuration_id(&conn, name_or_id) .await?; - Ok(LldpServiceConfig::new(true, Some(config_id))) - } - Lldp::Disabled => { - Ok(LldpServiceConfig::new(false, None)) + Ok::( + LldpServiceConfig::new(true, Some(config_id)), + ) } + None => Ok(LldpServiceConfig::new(false, None)), }?; diesel::insert_into(lldp_service_dsl::lldp_service_config) @@ -868,6 +852,7 @@ impl DataStore { name_or_id: NameOrId, link: Name, ) -> DeleteResult { + use db::schema::lldp_service_config::dsl as lldp_service_dsl; use db::schema::switch_port_settings_link_config::dsl as link_dsl; let conn = self.pool_connection_authorized(opctx).await?; @@ -882,13 +867,22 @@ impl DataStore { let parent_id = switch_port_configuration_id(&conn, identity).await?; - // delete lldp service config - todo!("delete lldp service config"); - // delete child record - diesel::delete(link_dsl::switch_port_settings_link_config) - .filter(link_dsl::port_settings_id.eq(parent_id)) - .filter(link_dsl::link_name.eq(link_name.to_string())) + let config = diesel::delete( + link_dsl::switch_port_settings_link_config, + ) + .filter(link_dsl::port_settings_id.eq(parent_id)) + .filter(link_dsl::link_name.eq(link_name.to_string())) + .returning(SwitchPortLinkConfig::as_returning()) + .get_result_async(&conn) + .await?; + + // delete lldp service configuration + diesel::delete(lldp_service_dsl::lldp_service_config) + .filter( + lldp_service_dsl::id + .eq(config.lldp_service_config_id), + ) .execute_async(&conn) .await?; diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 16657aa03d4..9b20cd1a691 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1567,8 +1567,8 @@ pub struct NamedLinkConfigCreate { /// Maximum transmission unit for the link. pub mtu: u16, - /// The link-layer discovery protocol (LLDP) configuration for the link. - pub lldp: LldpServiceConfigCreate, + /// The optional link-layer discovery protocol (LLDP) configuration for the link. + pub lldp_config: Option, /// The forward error correction mode of the link. pub fec: LinkFec, diff --git a/openapi/nexus.json b/openapi/nexus.json index 99b6fecdbc5..6f13b35cc01 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7370,7 +7370,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LinkConfigCreate" + "$ref": "#/components/schemas/NamedLinkConfigCreate" } } }, @@ -17585,6 +17585,62 @@ } ] }, + "NamedLinkConfigCreate": { + "description": "Named switch link configuration.", + "type": "object", + "properties": { + "autoneg": { + "description": "Whether or not to set autonegotiation", + "type": "boolean" + }, + "fec": { + "description": "The forward error correction mode of the link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkFec" + } + ] + }, + "lldp_config": { + "nullable": true, + "description": "The optional link-layer discovery protocol (LLDP) configuration for the link.", + "allOf": [ + { + "$ref": "#/components/schemas/NameOrId" + } + ] + }, + "mtu": { + "description": "Maximum transmission unit for the link.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "name": { + "description": "Name of link", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "speed": { + "description": "The speed of the link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkSpeed" + } + ] + } + }, + "required": [ + "autoneg", + "fec", + "mtu", + "name", + "speed" + ] + }, "NetworkInterface": { "description": "Information required to construct a virtual network interface", "type": "object", From 6eb4be4a776243de5da4bd3d63f8870974252975 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Tue, 27 Aug 2024 23:36:22 +0000 Subject: [PATCH 14/37] WIP: interface address management --- nexus/db-model/src/schema.rs | 1 + .../src/db/datastore/switch_port.rs | 442 +++++++++++++++++- nexus/src/app/switch_port.rs | 17 + nexus/src/external_api/http_entrypoints.rs | 220 ++------- nexus/types/src/external_api/params.rs | 7 +- 5 files changed, 509 insertions(+), 178 deletions(-) diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index 75f0686a34d..fd996210373 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -1909,4 +1909,5 @@ allow_tables_to_appear_in_same_query!( switch_port_settings, switch_port_settings_port_config, switch_port_settings_link_config, + switch_port_settings_address_config, ); diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index f442bab5a32..0197fd1fce6 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -32,11 +32,12 @@ use diesel::{ use diesel_dtrace::DTraceConnection; use ipnetwork::IpNetwork; use nexus_db_model::{ - BgpConfig, SqlU16, SqlU32, SqlU8, SwitchPortBgpPeerConfigAllowExport, - SwitchPortBgpPeerConfigAllowImport, SwitchPortBgpPeerConfigCommunity, - SwitchPortGeometry, + AddressLot, BgpConfig, SqlU16, SqlU32, SqlU8, + SwitchPortBgpPeerConfigAllowExport, SwitchPortBgpPeerConfigAllowImport, + SwitchPortBgpPeerConfigCommunity, SwitchPortGeometry, }; use nexus_types::external_api::params; +use nexus_types::identity::Asset; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ self, CreateResult, DataPageParams, DeleteResult, Error, @@ -899,6 +900,440 @@ impl DataStore { Ok(()) } + pub async fn switch_port_configuration_interface_address_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + interface: Name, + ) -> ListResultVec { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings_address_config as address_config; + + let dataset = + port_settings::table + .inner_join(address_config::table.on( + address_config::port_settings_id.eq(port_settings::id), + )); + + let query = match configuration { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + let configs: Vec = query + .select(SwitchPortAddressConfig::as_select()) + .filter(address_config::interface_name.eq(interface)) + .load_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e: diesel::result::Error| { + let msg = + "error while looking up interface address configuration"; + match e { + diesel::result::Error::NotFound => { + Error::non_resourcetype_not_found( + "could not find address configuration for switch port configuration: {configuration}, interface: {interface}" + )}, + _ => Error::internal_error(msg), + } + })?; + + Ok(configs) + } + + // TODO: if this isn't too annoying we can fill this out + // pub async fn switch_port_configuration_interface_address_set( + // &self, + // opctx: &OpContext, + // configuration: NameOrId, + // interface: Name, + // new_config: params::AddressConfig, + // ) -> ListResultVec { + // let conn = self.pool_connection_authorized(opctx).await?; + // let err = OptionalError::new(); + + // let config = self + // .transaction_retry_wrapper( + // "switch_port_configuration_interface_address_add", + // ) + // .transaction(&conn, |conn| { + // let err = err.clone(); + + // async move { + + // let mut address_config = Vec::new(); + // use db::schema::address_lot; + // for address in &new_config.addresses { + // let address_lot_id = match &address.address_lot { + // NameOrId::Id(id) => *id, + // NameOrId::Name(name) => { + // let name = name.to_string(); + // address_lot_dsl::address_lot + // .filter(address_lot::time_deleted.is_null()) + // .filter(address_lot::name.eq(name)) + // .select(address_lot::id) + // .limit(1) + // .first_async::(conn) + // .await + // .map_err(|diesel_error| { + // err.bail_retryable_or( + // diesel_error, + // SwitchPortSettingsCreateError::AddressLotNotFound + // ) + // })? + // } + // }; + // // TODO: Reduce DB round trips needed for reserving ip blocks + // // https://github.com/oxidecomputer/omicron/issues/3060 + // let (block, rsvd_block) = + // crate::db::datastore::address_lot::try_reserve_block( + // address_lot_id, + // address.address.addr().into(), + // // TODO: Should we allow anycast addresses for switch_ports? + // // anycast + // false, + // &conn, + // ) + // .await + // .map_err(|e| match e { + // ReserveBlockTxnError::CustomError(e) => { + // err.bail(SwitchPortSettingsCreateError::ReserveBlock(e)) + // } + // ReserveBlockTxnError::Database(e) => e, + // })?; + + // address_config.push(SwitchPortAddressConfig::new( + // psid, + // block.id, + // rsvd_block.id, + // address.address.into(), + // interface_name.clone(), + // address.vlan_id, + // )); + // } + // result.addresses = diesel::insert_into( + // address_config_dsl::switch_port_settings_address_config, + // ) + // .values(address_config) + // .returning(SwitchPortAddressConfig::as_returning()) + // .get_results_async(conn) + // .await?; + + // } + + // }) + // .await + // .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + // Ok(config) + // } + + pub async fn switch_port_configuration_interface_address_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + interface: Name, + address: params::Address, + ) -> CreateResult { + use db::schema::address_lot; + use db::schema::switch_port_settings_address_config as address_config; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_interface_address_add", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let interface = interface.clone(); + let new_settings = address.clone(); + let err = err.clone(); + + async move { + + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up address lot for interface address" + )) + }, + } + })?; + + // resolve id of referenced address lot + let address_lot_id = match new_settings.address_lot { + + NameOrId::Id(id) => { + // verify id is valid + address_lot::table + .filter(address_lot::time_deleted.is_null()) + .filter(address_lot::id.eq(id)) + .select(address_lot::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail(Error::not_found_by_id(ResourceType::AddressLot, &id)) + }, + _ => { + let message = "error while looking up address lot for interface address"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + }, + } + }) + }, + + NameOrId::Name(name) => { + address_lot::table + .filter(address_lot::time_deleted.is_null()) + .filter(address_lot::name.eq(name.to_string())) + .select(address_lot::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail(Error::not_found_by_name(ResourceType::AddressLot, &name)) + }, + _ => { + let message = "error while looking up address lot for interface address"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + }, + } + }) + } + }?; + + // create a reservation + let (block, rsvd_block) = + crate::db::datastore::address_lot::try_reserve_block( + address_lot_id, + new_settings.address.addr().into(), + // TODO: Should we allow anycast addresses for switch_ports? + // anycast + false, + &conn, + ) + .await + .map_err(|e| match e { + ReserveBlockTxnError::CustomError(e) => { + let message = match e { + ReserveBlockError::AddressUnavailable => "address is unavailable", + ReserveBlockError::AddressNotInLot => "address is not in lot", + }; + err.bail(Error::conflict(message)) + } + ReserveBlockTxnError::Database(e) => { + let message = "error while reserving address"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + }, + })?; + + let address_config = SwitchPortAddressConfig { + port_settings_id, + address_lot_block_id: block.id, + rsvd_address_lot_block_id: rsvd_block.id, + address: new_settings.address.into(), + interface_name: interface.to_string(), + vlan_id: new_settings.vlan_id.map(|i| i.into()), + }; + + let address = diesel::insert_into( + address_config::table, + ) + .values(address_config) + .returning(SwitchPortAddressConfig::as_returning()) + .get_result_async(&conn) + .await?; + + Ok(address) + } + }) + .await + .map_err(|e| { + let message = "switch_port_configuration_interface_address_add failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + }, + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error("error while adding address to interface") + }, + } + }) + } + + pub async fn switch_port_configuration_interface_address_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + interface: Name, + address: params::Address, + ) -> DeleteResult { + use db::schema::address_lot; + use db::schema::switch_port_settings_address_config as address_config; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_interface_address_remove", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let interface = interface.clone(); + let new_settings = address.clone(); + let err = err.clone(); + + async move { + + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up address lot for interface address" + )) + }, + } + })?; + + // resolve id of referenced address lot + let address_lot_id = match new_settings.address_lot { + + NameOrId::Id(id) => { + // verify id is valid + address_lot::table + .filter(address_lot::time_deleted.is_null()) + .filter(address_lot::id.eq(id)) + .select(address_lot::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail(Error::not_found_by_id(ResourceType::AddressLot, &id)) + }, + _ => { + let message = "error while looking up address lot for interface address"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + }, + } + }) + }, + + NameOrId::Name(name) => { + address_lot::table + .filter(address_lot::time_deleted.is_null()) + .filter(address_lot::name.eq(name.to_string())) + .select(address_lot::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail(Error::not_found_by_name(ResourceType::AddressLot, &name)) + }, + _ => { + let message = "error while looking up address lot for interface address"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + }, + } + }) + } + }?; + + // delete reservation + let (block, rsvd_block) = + crate::db::datastore::address_lot::try_reserve_block( + address_lot_id, + new_settings.address.addr().into(), + // TODO: Should we allow anycast addresses for switch_ports? + // anycast + false, + &conn, + ) + .await + .map_err(|e| match e { + ReserveBlockTxnError::CustomError(e) => { + let message = match e { + ReserveBlockError::AddressUnavailable => "address is unavailable", + ReserveBlockError::AddressNotInLot => "address is not in lot", + }; + err.bail(Error::conflict(message)) + } + ReserveBlockTxnError::Database(e) => { + let message = "error while reserving address"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + }, + })?; + + let address_config = SwitchPortAddressConfig { + port_settings_id, + address_lot_block_id: block.id, + rsvd_address_lot_block_id: rsvd_block.id, + address: new_settings.address.into(), + interface_name: interface.to_string(), + vlan_id: new_settings.vlan_id.map(|i| i.into()), + }; + + let address = diesel::insert_into( + address_config::table, + ) + .values(address_config) + .returning(SwitchPortAddressConfig::as_returning()) + .get_result_async(&conn) + .await?; + + Ok(address) + } + }) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + Ok(()) + } + // switch ports pub async fn switch_port_create( @@ -1889,6 +2324,7 @@ async fn do_switch_port_settings_delete( Ok(()) } +// TODO: refactor to emit more detailed errors async fn switch_port_configuration_id( conn: &async_bb8_diesel::Connection>, name_or_id: NameOrId, diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 05f246c5e02..06fb6f927f1 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -8,6 +8,7 @@ use db::datastore::SwitchPortSettingsCombinedResult; use dpd_client::types::LinkId; use dpd_client::types::PortId; use http::StatusCode; +use nexus_db_model::SwitchPortAddressConfig; use nexus_db_model::SwitchPortConfig; use nexus_db_model::SwitchPortGeometry; use nexus_db_model::SwitchPortLinkConfig; @@ -208,6 +209,22 @@ impl super::Nexus { .await } + pub(crate) async fn switch_port_configuration_interface_address_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + interface: Name, + ) -> ListResultVec { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_interface_address_list( + opctx, + configuration, + interface.into(), + ) + .await + } + async fn switch_port_create( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index ee210a3fb83..3499830f3fd 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -267,11 +267,6 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register(networking_loopback_address_delete)?; api.register(networking_loopback_address_list)?; - api.register(networking_switch_port_configuration_create)?; - api.register(networking_switch_port_configuration_delete)?; - api.register(networking_switch_port_configuration_view)?; - api.register(networking_switch_port_configuration_list)?; - // TODO: Levon - Group composition (omicron#4405)? // /v1/system/networking/switch-port-configuration-group/{name_or_id}/.. @@ -291,60 +286,44 @@ pub(crate) fn external_api() -> NexusApiDescription { // TODO: Levon - test api.register(networking_switch_port_configuration_link_list)?; - // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface - // TODO: Levon - test - api.register( - networking_switch_port_configuration_link_interface_create, - )?; + // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/address // TODO: Levon - test api.register( - networking_switch_port_configuration_link_interface_delete, + networking_switch_port_configuration_interface_address_add, )?; // TODO: Levon - test - api.register(networking_switch_port_configuration_link_interface_view)?; - // TODO: Levon - test - api.register(networking_switch_port_configuration_link_interface_list)?; - - // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/address - // TODO: Levon - test api.register( - networking_switch_port_configuration_link_interface_address_add, + networking_switch_port_configuration_interface_address_remove, )?; // TODO: Levon - test api.register( - networking_switch_port_configuration_link_interface_address_remove, - )?; - // TODO: Levon - test - api.register( - networking_switch_port_configuration_link_interface_address_list, + networking_switch_port_configuration_interface_address_list, )?; - // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/route + // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/route // TODO: Levon - test - api.register( - networking_switch_port_configuration_link_interface_route_add, - )?; + api.register(networking_switch_port_configuration_interface_route_add)?; // TODO: Levon - test api.register( - networking_switch_port_configuration_link_interface_route_remove, + networking_switch_port_configuration_interface_route_remove, )?; // TODO: Levon - test api.register( - networking_switch_port_configuration_link_interface_route_list, + networking_switch_port_configuration_interface_route_list, )?; - // /v1/system/networking/switch-port-configuration/{name_or_id}/link/{link}/interface/{interface}/bgp-peer + // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/bgp-peer // TODO: Levon - test api.register( - networking_switch_port_configuration_link_interface_bgp_peer_add, + networking_switch_port_configuration_interface_bgp_peer_add, )?; // TODO: Levon - test api.register( - networking_switch_port_configuration_link_interface_bgp_peer_remove, + networking_switch_port_configuration_interface_bgp_peer_remove, )?; // TODO: Levon - test api.register( - networking_switch_port_configuration_link_interface_bgp_peer_list, + networking_switch_port_configuration_interface_bgp_peer_list, )?; api.register(networking_switch_port_list)?; @@ -4065,131 +4044,32 @@ async fn networking_switch_port_configuration_link_delete( .await } -/// List interfaces for a provided switch port link configuration -#[endpoint { - method = GET, - path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface", - tags = ["system/networking"], -}] -async fn networking_switch_port_configuration_link_interface_list( - rqctx: RequestContext, - path_params: Path, - query_params: Query< - PaginatedById, - >, -) -> Result>, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; - let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("list interfaces") - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await -} - -/// Create interface configuration for a provided switch port link configuration -#[endpoint { - method = POST, - path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface", - tags = ["system/networking"], -}] -async fn networking_switch_port_configuration_link_interface_create( - rqctx: RequestContext, - path_params: Path, - new_settings: TypedBody, -) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; - let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("create interface") - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await -} - -/// View interface configuration for a provided switch port link configuration -#[endpoint { - method = GET, - path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}", - tags = ["system/networking"], -}] -async fn networking_switch_port_configuration_link_interface_view( - rqctx: RequestContext, - path_params: Path, -) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; - let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("delete interface") - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await -} - -/// Delete interface configuration for a provided switch port link configuration -#[endpoint { - method = DELETE, - path = "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}", - tags = ["system/networking"], -}] -async fn networking_switch_port_configuration_link_interface_delete( - rqctx: RequestContext, - path_params: Path, -) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; - let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("delete interface") - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await -} - /// List addresses assigned to a provided interface configuration #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address", + path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_link_interface_address_list( +async fn networking_switch_port_configuration_interface_address_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let params::SwitchPortSettingsInterfaceInfoSelector { + configuration, + interface, + } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; + let settings = nexus + .switch_port_configuration_interface_address_list( + &opctx, + configuration, + interface, + ) + .await?; todo!("list interface addresses") }; apictx @@ -4202,12 +4082,12 @@ async fn networking_switch_port_configuration_link_interface_address_list( /// Add address to an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/add", + path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/add", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_link_interface_address_add( +async fn networking_switch_port_configuration_interface_address_add( rqctx: RequestContext, - path_params: Path, + path_params: Path, address: TypedBody, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -4229,12 +4109,12 @@ async fn networking_switch_port_configuration_link_interface_address_add( /// Remove address from an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/remove", + path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/remove", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_link_interface_address_remove( +async fn networking_switch_port_configuration_interface_address_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, address: TypedBody, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -4256,12 +4136,12 @@ async fn networking_switch_port_configuration_link_interface_address_remove( /// List routes assigned to a provided interface configuration #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route", + path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_link_interface_route_list( +async fn networking_switch_port_configuration_interface_route_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4282,12 +4162,12 @@ async fn networking_switch_port_configuration_link_interface_route_list( /// Add route to an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/add", + path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/add", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_link_interface_route_add( +async fn networking_switch_port_configuration_interface_route_add( rqctx: RequestContext, - path_params: Path, + path_params: Path, route: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); @@ -4309,12 +4189,12 @@ async fn networking_switch_port_configuration_link_interface_route_add( /// Remove address from an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/remove", + path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/remove", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_link_interface_route_remove( +async fn networking_switch_port_configuration_interface_route_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, route: TypedBody, ) -> Result { let apictx = rqctx.context(); @@ -4336,12 +4216,12 @@ async fn networking_switch_port_configuration_link_interface_route_remove( /// List bgp peers assigned to a provided interface configuration #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer", + path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_link_interface_bgp_peer_list( +async fn networking_switch_port_configuration_interface_bgp_peer_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4362,12 +4242,12 @@ async fn networking_switch_port_configuration_link_interface_bgp_peer_list( /// Add bgp peer to an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/add", + path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_link_interface_bgp_peer_add( +async fn networking_switch_port_configuration_interface_bgp_peer_add( rqctx: RequestContext, - path_params: Path, + path_params: Path, bgp_peer: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); @@ -4389,12 +4269,12 @@ async fn networking_switch_port_configuration_link_interface_bgp_peer_add( /// Remove bgp peer from an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/remove", + path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_link_interface_bgp_peer_remove( +async fn networking_switch_port_configuration_interface_bgp_peer_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, bgp_peer: TypedBody, ) -> Result { let apictx = rqctx.context(); diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 4534376f77d..742d8ae0f74 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1799,15 +1799,12 @@ pub struct SwitchPortSettingsLinkInfoSelector { pub link: Name, } -/// Select an interface settings info object by port settings name, link name, and interface name. +/// Select an interface settings info object by port settings name / id and interface name. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SwitchPortSettingsLinkInterfaceInfoSelector { +pub struct SwitchPortSettingsInterfaceInfoSelector { /// A name or id to use when selecting a switch port configuration. pub configuration: NameOrId, - /// Link name - pub link: Name, - /// Interface name pub interface: Name, } From 69ff1f0db3a833500d065a7208a3623516609bea Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 29 Aug 2024 00:41:09 +0000 Subject: [PATCH 15/37] WIP: interface address management --- .../src/db/datastore/switch_port.rs | 185 +- nexus/src/app/switch_port.rs | 36 + nexus/src/external_api/http_entrypoints.rs | 60 +- nexus/tests/output/nexus_tags.txt | 26 +- .../output/uncovered-authz-endpoints.txt | 22 +- .../output/unexpected-authz-endpoints.txt | 4 + openapi/nexus.json | 1571 +++-------------- 7 files changed, 366 insertions(+), 1538 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 0197fd1fce6..32f6b68c28f 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -32,12 +32,11 @@ use diesel::{ use diesel_dtrace::DTraceConnection; use ipnetwork::IpNetwork; use nexus_db_model::{ - AddressLot, BgpConfig, SqlU16, SqlU32, SqlU8, - SwitchPortBgpPeerConfigAllowExport, SwitchPortBgpPeerConfigAllowImport, - SwitchPortBgpPeerConfigCommunity, SwitchPortGeometry, + BgpConfig, SqlU16, SqlU32, SqlU8, SwitchPortBgpPeerConfigAllowExport, + SwitchPortBgpPeerConfigAllowImport, SwitchPortBgpPeerConfigCommunity, + SwitchPortGeometry, }; use nexus_types::external_api::params; -use nexus_types::identity::Asset; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ self, CreateResult, DataPageParams, DeleteResult, Error, @@ -948,93 +947,6 @@ impl DataStore { Ok(configs) } - // TODO: if this isn't too annoying we can fill this out - // pub async fn switch_port_configuration_interface_address_set( - // &self, - // opctx: &OpContext, - // configuration: NameOrId, - // interface: Name, - // new_config: params::AddressConfig, - // ) -> ListResultVec { - // let conn = self.pool_connection_authorized(opctx).await?; - // let err = OptionalError::new(); - - // let config = self - // .transaction_retry_wrapper( - // "switch_port_configuration_interface_address_add", - // ) - // .transaction(&conn, |conn| { - // let err = err.clone(); - - // async move { - - // let mut address_config = Vec::new(); - // use db::schema::address_lot; - // for address in &new_config.addresses { - // let address_lot_id = match &address.address_lot { - // NameOrId::Id(id) => *id, - // NameOrId::Name(name) => { - // let name = name.to_string(); - // address_lot_dsl::address_lot - // .filter(address_lot::time_deleted.is_null()) - // .filter(address_lot::name.eq(name)) - // .select(address_lot::id) - // .limit(1) - // .first_async::(conn) - // .await - // .map_err(|diesel_error| { - // err.bail_retryable_or( - // diesel_error, - // SwitchPortSettingsCreateError::AddressLotNotFound - // ) - // })? - // } - // }; - // // TODO: Reduce DB round trips needed for reserving ip blocks - // // https://github.com/oxidecomputer/omicron/issues/3060 - // let (block, rsvd_block) = - // crate::db::datastore::address_lot::try_reserve_block( - // address_lot_id, - // address.address.addr().into(), - // // TODO: Should we allow anycast addresses for switch_ports? - // // anycast - // false, - // &conn, - // ) - // .await - // .map_err(|e| match e { - // ReserveBlockTxnError::CustomError(e) => { - // err.bail(SwitchPortSettingsCreateError::ReserveBlock(e)) - // } - // ReserveBlockTxnError::Database(e) => e, - // })?; - - // address_config.push(SwitchPortAddressConfig::new( - // psid, - // block.id, - // rsvd_block.id, - // address.address.into(), - // interface_name.clone(), - // address.vlan_id, - // )); - // } - // result.addresses = diesel::insert_into( - // address_config_dsl::switch_port_settings_address_config, - // ) - // .values(address_config) - // .returning(SwitchPortAddressConfig::as_returning()) - // .get_results_async(conn) - // .await?; - - // } - - // }) - // .await - // .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; - - // Ok(config) - // } - pub async fn switch_port_configuration_interface_address_add( &self, opctx: &OpContext, @@ -1209,7 +1121,7 @@ impl DataStore { .transaction(&conn, |conn| { let parent_configuration = configuration.clone(); let interface = interface.clone(); - let new_settings = address.clone(); + let settings_to_remove = address.clone(); let err = err.clone(); async move { @@ -1235,7 +1147,7 @@ impl DataStore { })?; // resolve id of referenced address lot - let address_lot_id = match new_settings.address_lot { + let address_lot_id = match settings_to_remove.address_lot { NameOrId::Id(id) => { // verify id is valid @@ -1283,55 +1195,62 @@ impl DataStore { } }?; - // delete reservation - let (block, rsvd_block) = - crate::db::datastore::address_lot::try_reserve_block( - address_lot_id, - new_settings.address.addr().into(), - // TODO: Should we allow anycast addresses for switch_ports? - // anycast - false, - &conn, - ) + // find address config + let found_address_config = address_config::table + .filter(address_config::address.eq(IpNetwork::from(settings_to_remove.address))) + .filter(address_config::port_settings_id.eq(port_settings_id)) + .filter(address_config::address_lot_block_id.eq(address_lot_id)) + .filter(address_config::interface_name.eq(interface.clone())) + .select(SwitchPortAddressConfig::as_select()) + .limit(1) + .first_async::(&conn) .await - .map_err(|e| match e { - ReserveBlockTxnError::CustomError(e) => { - let message = match e { - ReserveBlockError::AddressUnavailable => "address is unavailable", - ReserveBlockError::AddressNotInLot => "address is not in lot", - }; - err.bail(Error::conflict(message)) + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail(Error::non_resourcetype_not_found("unable to find requested address config")) + }, + _ => { + let message = "error while looking up address lot for interface address"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + }, } - ReserveBlockTxnError::Database(e) => { - let message = "error while reserving address"; - error!(opctx.log, "{message}"; "error" => ?e); - err.bail(Error::internal_error(message)) - }, })?; - let address_config = SwitchPortAddressConfig { - port_settings_id, - address_lot_block_id: block.id, - rsvd_address_lot_block_id: rsvd_block.id, - address: new_settings.address.into(), - interface_name: interface.to_string(), - vlan_id: new_settings.vlan_id.map(|i| i.into()), - }; + // delete reservation + use db::schema::address_lot_rsvd_block as rsvd_block; + diesel::delete(rsvd_block::table) + .filter(rsvd_block::id.eq(found_address_config.rsvd_address_lot_block_id)) + .execute_async(&conn) + .await?; - let address = diesel::insert_into( - address_config::table, - ) - .values(address_config) - .returning(SwitchPortAddressConfig::as_returning()) - .get_result_async(&conn) - .await?; + // delete address config + diesel::delete(address_config::table) + .filter(address_config::address.eq(IpNetwork::from(settings_to_remove.address))) + .filter(address_config::port_settings_id.eq(port_settings_id)) + .filter(address_config::address_lot_block_id.eq(address_lot_id)) + .filter(address_config::interface_name.eq(interface)) + .execute_async(&conn) + .await?; - Ok(address) + Ok(()) } }) .await - .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; - Ok(()) + .map_err(|e| { + let message = "switch_port_configuration_interface_address_remove failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + }, + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error("error while removing address from interface") + }, + } + }) } // switch ports diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 06fb6f927f1..5b4a4241549 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -225,6 +225,42 @@ impl super::Nexus { .await } + pub(crate) async fn switch_port_configuration_interface_address_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + interface: Name, + address: params::Address, + ) -> CreateResult { + opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_interface_address_add( + opctx, + configuration, + interface.into(), + address, + ) + .await + } + + pub(crate) async fn switch_port_configuration_interface_address_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + interface: Name, + address: params::Address, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_interface_address_remove( + opctx, + configuration, + interface.into(), + address, + ) + .await + } + async fn switch_port_create( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 3499830f3fd..e9d97ca76d2 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -44,11 +44,6 @@ use nexus_types::external_api::{ params::{BgpPeerConfig, RouteConfig}, shared::{BfdStatus, ProbeInfo}, }; -use omicron_common::api::external::http_pagination::{ - data_page_params_for, marker_for_name, marker_for_name_or_id, - name_or_id_pagination, PaginatedBy, PaginatedById, PaginatedByName, - PaginatedByNameOrId, ScanById, ScanByName, ScanByNameOrId, ScanParams, -}; use omicron_common::api::external::AddressLot; use omicron_common::api::external::AddressLotBlock; use omicron_common::api::external::AddressLotCreateResponse; @@ -71,7 +66,6 @@ use omicron_common::api::external::NameOrId; use omicron_common::api::external::Probe; use omicron_common::api::external::RouterRoute; use omicron_common::api::external::RouterRouteKind; -use omicron_common::api::external::SwitchInterfaceConfig; use omicron_common::api::external::SwitchPort; use omicron_common::api::external::SwitchPortConfig; use omicron_common::api::external::SwitchPortLinkConfig; @@ -81,6 +75,14 @@ use omicron_common::api::external::TufRepoGetResponse; use omicron_common::api::external::TufRepoInsertResponse; use omicron_common::api::external::VpcFirewallRuleUpdateParams; use omicron_common::api::external::VpcFirewallRules; +use omicron_common::api::external::{ + http_pagination::{ + data_page_params_for, marker_for_name, marker_for_name_or_id, + name_or_id_pagination, PaginatedBy, PaginatedById, PaginatedByName, + PaginatedByNameOrId, ScanById, ScanByName, ScanByNameOrId, ScanParams, + }, + SwitchPortAddressConfig, +}; use omicron_common::bail_unless; use omicron_uuid_kinds::GenericUuid; use parse_display::Display; @@ -4053,7 +4055,7 @@ async fn networking_switch_port_configuration_link_delete( async fn networking_switch_port_configuration_interface_address_list( rqctx: RequestContext, path_params: Path, -) -> Result>, HttpError> { +) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4070,7 +4072,7 @@ async fn networking_switch_port_configuration_interface_address_list( interface, ) .await?; - todo!("list interface addresses") + Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) }; apictx .context @@ -4088,16 +4090,27 @@ async fn networking_switch_port_configuration_interface_address_list( async fn networking_switch_port_configuration_interface_address_add( rqctx: RequestContext, path_params: Path, - address: TypedBody, -) -> Result>, HttpError> { + address: TypedBody, +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let params::SwitchPortSettingsInterfaceInfoSelector { + configuration, + interface, + } = path_params.into_inner(); + let address = address.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("add interface address") + let settings = nexus + .switch_port_configuration_interface_address_add( + &opctx, + configuration, + interface, + address, + ) + .await?; + Ok(HttpResponseCreated(settings.into())) }; apictx .context @@ -4115,16 +4128,27 @@ async fn networking_switch_port_configuration_interface_address_add( async fn networking_switch_port_configuration_interface_address_remove( rqctx: RequestContext, path_params: Path, - address: TypedBody, -) -> Result>, HttpError> { + address: TypedBody, +) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let params::SwitchPortSettingsInterfaceInfoSelector { + configuration, + interface, + } = path_params.into_inner(); + let address = address.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("remove interface address") + nexus + .switch_port_configuration_interface_address_remove( + &opctx, + configuration, + interface, + address, + ) + .await?; + Ok(HttpResponseDeleted()) }; apictx .context diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index ffe1b0b8c82..8cd73db3ea4 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -194,29 +194,21 @@ networking_bgp_status GET /v1/system/networking/bgp-stat networking_loopback_address_create POST /v1/system/networking/loopback-address networking_loopback_address_delete DELETE /v1/system/networking/loopback-address/{rack_id}/{switch_location}/{address}/{subnet_mask} networking_loopback_address_list GET /v1/system/networking/loopback-address -networking_switch_port_configuration_create POST /v1/system/networking/switch-port-configuration -networking_switch_port_configuration_delete DELETE /v1/system/networking/switch-port-configuration networking_switch_port_configuration_geometry_set POST /v1/system/networking/switch-port-configuration/{configuration}/geometry networking_switch_port_configuration_geometry_view GET /v1/system/networking/switch-port-configuration/{configuration}/geometry +networking_switch_port_configuration_interface_address_add POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/add +networking_switch_port_configuration_interface_address_list GET /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address +networking_switch_port_configuration_interface_address_remove POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/remove +networking_switch_port_configuration_interface_bgp_peer_add POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add +networking_switch_port_configuration_interface_bgp_peer_list GET /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer +networking_switch_port_configuration_interface_bgp_peer_remove POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove +networking_switch_port_configuration_interface_route_add POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/add +networking_switch_port_configuration_interface_route_list GET /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route +networking_switch_port_configuration_interface_route_remove POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/remove networking_switch_port_configuration_link_create POST /v1/system/networking/switch-port-configuration/{configuration}/link networking_switch_port_configuration_link_delete DELETE /v1/system/networking/switch-port-configuration/{configuration}/link/{link} -networking_switch_port_configuration_link_interface_address_add POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/add -networking_switch_port_configuration_link_interface_address_list GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address -networking_switch_port_configuration_link_interface_address_remove POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/remove -networking_switch_port_configuration_link_interface_bgp_peer_add POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/add -networking_switch_port_configuration_link_interface_bgp_peer_list GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer -networking_switch_port_configuration_link_interface_bgp_peer_remove POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/remove -networking_switch_port_configuration_link_interface_create POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface -networking_switch_port_configuration_link_interface_delete DELETE /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface} -networking_switch_port_configuration_link_interface_list GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface -networking_switch_port_configuration_link_interface_route_add POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/add -networking_switch_port_configuration_link_interface_route_list GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route -networking_switch_port_configuration_link_interface_route_remove POST /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/remove -networking_switch_port_configuration_link_interface_view GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface} networking_switch_port_configuration_link_list GET /v1/system/networking/switch-port-configuration/{configuration}/link networking_switch_port_configuration_link_view GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link} -networking_switch_port_configuration_list GET /v1/system/networking/switch-port-configuration -networking_switch_port_configuration_view GET /v1/system/networking/switch-port-configuration/{configuration} API operations found with tag "system/silos" OPERATION ID METHOD URL PATH diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index 91b0b4eb37d..aaab1f0401f 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -1,19 +1,16 @@ API endpoints with no coverage in authz tests: probe_delete (delete "/experimental/v1/probes/{probe}") networking_switch_port_configuration_link_delete (delete "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}") -networking_switch_port_configuration_link_interface_delete (delete "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}") probe_list (get "/experimental/v1/probes") probe_view (get "/experimental/v1/probes/{probe}") ping (get "/v1/ping") networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status") networking_switch_port_configuration_geometry_view (get "/v1/system/networking/switch-port-configuration/{configuration}/geometry") +networking_switch_port_configuration_interface_address_list (get "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address") +networking_switch_port_configuration_interface_bgp_peer_list (get "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer") +networking_switch_port_configuration_interface_route_list (get "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route") networking_switch_port_configuration_link_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link") networking_switch_port_configuration_link_view (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}") -networking_switch_port_configuration_link_interface_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface") -networking_switch_port_configuration_link_interface_view (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}") -networking_switch_port_configuration_link_interface_address_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address") -networking_switch_port_configuration_link_interface_bgp_peer_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer") -networking_switch_port_configuration_link_interface_route_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") device_access_token (post "/device/token") @@ -22,11 +19,10 @@ login_saml (post "/login/{silo_name}/saml/{provi login_local (post "/v1/login/{silo_name}/local") logout (post "/v1/logout") networking_switch_port_configuration_geometry_set (post "/v1/system/networking/switch-port-configuration/{configuration}/geometry") +networking_switch_port_configuration_interface_address_add (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/add") +networking_switch_port_configuration_interface_address_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/remove") +networking_switch_port_configuration_interface_bgp_peer_add (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add") +networking_switch_port_configuration_interface_bgp_peer_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove") +networking_switch_port_configuration_interface_route_add (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/add") +networking_switch_port_configuration_interface_route_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/remove") networking_switch_port_configuration_link_create (post "/v1/system/networking/switch-port-configuration/{configuration}/link") -networking_switch_port_configuration_link_interface_create (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface") -networking_switch_port_configuration_link_interface_address_add (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/add") -networking_switch_port_configuration_link_interface_address_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/remove") -networking_switch_port_configuration_link_interface_bgp_peer_add (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/add") -networking_switch_port_configuration_link_interface_bgp_peer_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/remove") -networking_switch_port_configuration_link_interface_route_add (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/add") -networking_switch_port_configuration_link_interface_route_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/remove") diff --git a/nexus/tests/output/unexpected-authz-endpoints.txt b/nexus/tests/output/unexpected-authz-endpoints.txt index 23235ecf641..42c8cea15a4 100644 --- a/nexus/tests/output/unexpected-authz-endpoints.txt +++ b/nexus/tests/output/unexpected-authz-endpoints.txt @@ -2,3 +2,7 @@ API endpoints tested by unauthorized.rs but not found in the OpenAPI spec: PUT "/v1/system/update/repository?file_name=demo-repo.zip" GET "/v1/system/update/repository/1.0.0" DELETE "/v1/system/networking/address-lot/parkinglot" +POST "/v1/system/networking/switch-port-configuration?port_settings=portofino" +GET "/v1/system/networking/switch-port-configuration?port_settings=portofino" +DELETE "/v1/system/networking/switch-port-configuration?port_settings=portofino" +GET "/v1/system/networking/switch-port-configuration/protofino" diff --git a/openapi/nexus.json b/openapi/nexus.json index aedc168cbb9..3f30e820d4d 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7100,48 +7100,22 @@ } } }, - "/v1/system/networking/switch-port-configuration": { + "/v1/system/networking/switch-port-configuration/{configuration}/geometry": { "get": { "tags": [ "system/networking" ], - "summary": "List switch port settings", - "operationId": "networking_switch_port_configuration_list", + "summary": "Get switch port geometry for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_geometry_view", "parameters": [ { - "in": "query", + "in": "path", "name": "configuration", - "description": "An optional name or id to use when selecting a switch port configuration.", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "query", - "name": "limit", - "description": "Maximum number of items returned by a single call", - "schema": { - "nullable": true, - "type": "integer", - "format": "uint32", - "minimum": 1 - } - }, - { - "in": "query", - "name": "page_token", - "description": "Token returned by previous call to retrieve the subsequent page", - "schema": { - "nullable": true, - "type": "string" - } - }, - { - "in": "query", - "name": "sort_by", - "schema": { - "$ref": "#/components/schemas/NameOrIdSortMode" - } } ], "responses": { @@ -7150,7 +7124,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsResultsPage" + "$ref": "#/components/schemas/SwitchPortConfig" } } } @@ -7161,22 +7135,30 @@ "5XX": { "$ref": "#/components/responses/Error" } - }, - "x-dropshot-pagination": { - "required": [] } }, "post": { "tags": [ "system/networking" ], - "summary": "Create switch port settings", - "operationId": "networking_switch_port_configuration_create", + "summary": "Set switch port geometry for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_geometry_set", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsCreate" + "$ref": "#/components/schemas/SwitchPortConfigCreate" } } }, @@ -7188,7 +7170,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsView" + "$ref": "#/components/schemas/SwitchPortConfig" } } } @@ -7200,43 +7182,15 @@ "$ref": "#/components/responses/Error" } } - }, - "delete": { - "tags": [ - "system/networking" - ], - "summary": "Delete switch port settings", - "operationId": "networking_switch_port_configuration_delete", - "parameters": [ - { - "in": "query", - "name": "configuration", - "description": "An optional name or id to use when selecting a switch port configuration.", - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - } - ], - "responses": { - "204": { - "description": "successful deletion" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } } }, - "/v1/system/networking/switch-port-configuration/{configuration}": { + "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address": { "get": { "tags": [ "system/networking" ], - "summary": "Get information about a named set of switch-port-settings", - "operationId": "networking_switch_port_configuration_view", + "summary": "List addresses assigned to a provided interface configuration", + "operationId": "networking_switch_port_configuration_interface_address_list", "parameters": [ { "in": "path", @@ -7246,6 +7200,15 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } } ], "responses": { @@ -7254,7 +7217,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsView" + "title": "Array_of_SwitchPortAddressConfig", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortAddressConfig" + } } } } @@ -7268,13 +7235,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/geometry": { - "get": { + "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/add": { + "post": { "tags": [ "system/networking" ], - "summary": "Get switch port geometry for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_geometry_view", + "summary": "Add address to an interface configuration", + "operationId": "networking_switch_port_configuration_interface_address_add", "parameters": [ { "in": "path", @@ -7284,15 +7251,34 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Address" + } + } + }, + "required": true + }, "responses": { - "200": { - "description": "successful operation", + "201": { + "description": "successful creation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortConfig" + "$ref": "#/components/schemas/SwitchPortAddressConfig" } } } @@ -7304,13 +7290,15 @@ "$ref": "#/components/responses/Error" } } - }, + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/remove": { "post": { "tags": [ "system/networking" ], - "summary": "Set switch port geometry for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_geometry_set", + "summary": "Remove address from an interface configuration", + "operationId": "networking_switch_port_configuration_interface_address_remove", "parameters": [ { "in": "path", @@ -7320,28 +7308,30 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortConfigCreate" + "$ref": "#/components/schemas/Address" } } }, "required": true }, "responses": { - "201": { - "description": "successful creation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchPortConfig" - } - } - } + "204": { + "description": "successful deletion" }, "4XX": { "$ref": "#/components/responses/Error" @@ -7352,13 +7342,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/link": { + "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer": { "get": { "tags": [ "system/networking" ], - "summary": "List links for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_link_list", + "summary": "List bgp peers assigned to a provided interface configuration", + "operationId": "networking_switch_port_configuration_interface_bgp_peer_list", "parameters": [ { "in": "path", @@ -7368,6 +7358,15 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } } ], "responses": { @@ -7376,11 +7375,7 @@ "content": { "application/json": { "schema": { - "title": "Array_of_SwitchPortLinkConfig", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortLinkConfig" - } + "$ref": "#/components/schemas/BgpPeerConfig" } } } @@ -7392,13 +7387,15 @@ "$ref": "#/components/responses/Error" } } - }, + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add": { "post": { "tags": [ "system/networking" ], - "summary": "Create a link for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_link_create", + "summary": "Add bgp peer to an interface configuration", + "operationId": "networking_switch_port_configuration_interface_bgp_peer_add", "parameters": [ { "in": "path", @@ -7408,13 +7405,22 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "path", + "name": "interface", + "description": "Interface name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NamedLinkConfigCreate" + "$ref": "#/components/schemas/BgpPeer" } } }, @@ -7426,7 +7432,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortLinkConfig" + "$ref": "#/components/schemas/BgpPeer" } } } @@ -7440,13 +7446,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}": { - "get": { + "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove": { + "post": { "tags": [ "system/networking" ], - "summary": "View a link for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_link_view", + "summary": "Remove bgp peer from an interface configuration", + "operationId": "networking_switch_port_configuration_interface_bgp_peer_remove", "parameters": [ { "in": "path", @@ -7459,25 +7465,28 @@ }, { "in": "path", - "name": "link", - "description": "Link name", + "name": "interface", + "description": "Interface name", "required": true, "schema": { "$ref": "#/components/schemas/Name" } } ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchPortLinkConfig" - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpPeer" } } }, + "required": true + }, + "responses": { + "204": { + "description": "successful deletion" + }, "4XX": { "$ref": "#/components/responses/Error" }, @@ -7485,13 +7494,15 @@ "$ref": "#/components/responses/Error" } } - }, - "delete": { + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route": { + "get": { "tags": [ "system/networking" ], - "summary": "Delete a link for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_link_delete", + "summary": "List routes assigned to a provided interface configuration", + "operationId": "networking_switch_port_configuration_interface_route_list", "parameters": [ { "in": "path", @@ -7504,8 +7515,8 @@ }, { "in": "path", - "name": "link", - "description": "Link name", + "name": "interface", + "description": "Interface name", "required": true, "schema": { "$ref": "#/components/schemas/Name" @@ -7513,8 +7524,15 @@ } ], "responses": { - "204": { - "description": "successful deletion" + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RouteConfig" + } + } + } }, "4XX": { "$ref": "#/components/responses/Error" @@ -7525,13 +7543,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface": { - "get": { + "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/add": { + "post": { "tags": [ "system/networking" ], - "summary": "List interfaces for a provided switch port link configuration", - "operationId": "networking_switch_port_configuration_link_interface_list", + "summary": "Add route to an interface configuration", + "operationId": "networking_switch_port_configuration_interface_route_add", "parameters": [ { "in": "path", @@ -7544,352 +7562,31 @@ }, { "in": "path", - "name": "link", - "description": "Link name", + "name": "interface", + "description": "Interface name", "required": true, "schema": { "$ref": "#/components/schemas/Name" - } - }, - { - "in": "query", - "name": "limit", - "description": "Maximum number of items returned by a single call", - "schema": { - "nullable": true, - "type": "integer", - "format": "uint32", - "minimum": 1 - } - }, - { - "in": "query", - "name": "page_token", - "description": "Token returned by previous call to retrieve the subsequent page", - "schema": { - "nullable": true, - "type": "string" - } - }, - { - "in": "query", - "name": "sort_by", - "schema": { - "$ref": "#/components/schemas/IdSortMode" - } - }, - { - "in": "query", - "name": "switch_port_id", - "description": "An optional switch port id to use when listing switch ports.", - "schema": { - "nullable": true, - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchInterfaceConfigResultsPage" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - }, - "x-dropshot-pagination": { - "required": [] - } - }, - "post": { - "tags": [ - "system/networking" - ], - "summary": "Create interface configuration for a provided switch port link configuration", - "operationId": "networking_switch_port_configuration_link_interface_create", - "parameters": [ - { - "in": "path", - "name": "configuration", - "description": "A name or id to use when selecting a switch port configuration.", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchInterfaceConfigCreate" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "successful creation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchInterfaceConfig" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}": { - "get": { - "tags": [ - "system/networking" - ], - "summary": "View interface configuration for a provided switch port link configuration", - "operationId": "networking_switch_port_configuration_link_interface_view", - "parameters": [ - { - "in": "path", - "name": "configuration", - "description": "A name or id to use when selecting a switch port configuration.", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchInterfaceConfig" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, - "delete": { - "tags": [ - "system/networking" - ], - "summary": "Delete interface configuration for a provided switch port link configuration", - "operationId": "networking_switch_port_configuration_link_interface_delete", - "parameters": [ - { - "in": "path", - "name": "configuration", - "description": "A name or id to use when selecting a switch port configuration.", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - } - ], - "responses": { - "201": { - "description": "successful creation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchInterfaceConfig" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address": { - "get": { - "tags": [ - "system/networking" - ], - "summary": "List addresses assigned to a provided interface configuration", - "operationId": "networking_switch_port_configuration_link_interface_address_list", - "parameters": [ - { - "in": "path", - "name": "configuration", - "description": "A name or id to use when selecting a switch port configuration.", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "title": "Array_of_SwitchInterfaceConfig", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchInterfaceConfig" - } - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/add": { - "post": { - "tags": [ - "system/networking" - ], - "summary": "Add address to an interface configuration", - "operationId": "networking_switch_port_configuration_link_interface_address_add", - "parameters": [ - { - "in": "path", - "name": "configuration", - "description": "A name or id to use when selecting a switch port configuration.", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddressConfig" - } + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Route" + } } }, "required": true }, "responses": { - "200": { - "description": "successful operation", + "201": { + "description": "successful creation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchInterfaceConfigResultsPage" + "$ref": "#/components/schemas/Route" } } } @@ -7903,13 +7600,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/address/remove": { + "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/remove": { "post": { "tags": [ "system/networking" ], "summary": "Remove address from an interface configuration", - "operationId": "networking_switch_port_configuration_link_interface_address_remove", + "operationId": "networking_switch_port_configuration_interface_route_remove", "parameters": [ { "in": "path", @@ -7928,37 +7625,21 @@ "schema": { "$ref": "#/components/schemas/Name" } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddressConfig" + "$ref": "#/components/schemas/Route" } } }, "required": true }, "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchInterfaceConfigResultsPage" - } - } - } + "204": { + "description": "successful deletion" }, "4XX": { "$ref": "#/components/responses/Error" @@ -7969,13 +7650,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer": { + "/v1/system/networking/switch-port-configuration/{configuration}/link": { "get": { "tags": [ "system/networking" ], - "summary": "List bgp peers assigned to a provided interface configuration", - "operationId": "networking_switch_port_configuration_link_interface_bgp_peer_list", + "summary": "List links for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_list", "parameters": [ { "in": "path", @@ -7985,24 +7666,6 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], "responses": { @@ -8011,7 +7674,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BgpPeerConfig" + "title": "Array_of_SwitchPortLinkConfig", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortLinkConfig" + } } } } @@ -8023,15 +7690,13 @@ "$ref": "#/components/responses/Error" } } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/add": { + }, "post": { "tags": [ "system/networking" ], - "summary": "Add bgp peer to an interface configuration", - "operationId": "networking_switch_port_configuration_link_interface_bgp_peer_add", + "summary": "Create a link for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_create", "parameters": [ { "in": "path", @@ -8041,31 +7706,13 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BgpPeer" + "$ref": "#/components/schemas/NamedLinkConfigCreate" } } }, @@ -8077,7 +7724,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BgpPeer" + "$ref": "#/components/schemas/SwitchPortLinkConfig" } } } @@ -8091,72 +7738,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/bgp-peer/remove": { - "post": { - "tags": [ - "system/networking" - ], - "summary": "Remove bgp peer from an interface configuration", - "operationId": "networking_switch_port_configuration_link_interface_bgp_peer_remove", - "parameters": [ - { - "in": "path", - "name": "configuration", - "description": "A name or id to use when selecting a switch port configuration.", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BgpPeer" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "successful deletion" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route": { + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}": { "get": { "tags": [ "system/networking" ], - "summary": "List routes assigned to a provided interface configuration", - "operationId": "networking_switch_port_configuration_link_interface_route_list", + "summary": "View a link for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_view", "parameters": [ { "in": "path", @@ -8167,15 +7755,6 @@ "$ref": "#/components/schemas/NameOrId" } }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, { "in": "path", "name": "link", @@ -8192,73 +7771,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RouteConfig" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/add": { - "post": { - "tags": [ - "system/networking" - ], - "summary": "Add route to an interface configuration", - "operationId": "networking_switch_port_configuration_link_interface_route_add", - "parameters": [ - { - "in": "path", - "name": "configuration", - "description": "A name or id to use when selecting a switch port configuration.", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Route" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "successful creation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Route" + "$ref": "#/components/schemas/SwitchPortLinkConfig" } } } @@ -8270,15 +7783,13 @@ "$ref": "#/components/responses/Error" } } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}/interface/{interface}/route/remove": { - "post": { + }, + "delete": { "tags": [ "system/networking" ], - "summary": "Remove address from an interface configuration", - "operationId": "networking_switch_port_configuration_link_interface_route_remove", + "summary": "Delete a link for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_delete", "parameters": [ { "in": "path", @@ -8289,35 +7800,16 @@ "$ref": "#/components/schemas/NameOrId" } }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - }, { "in": "path", "name": "link", "description": "Link name", "required": true, "schema": { - "$ref": "#/components/schemas/Name" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Route" - } + "$ref": "#/components/schemas/Name" } - }, - "required": true - }, + } + ], "responses": { "204": { "description": "successful deletion" @@ -10830,22 +10322,6 @@ "address_lot" ] }, - "AddressConfig": { - "description": "A set of addresses associated with a port configuration.", - "type": "object", - "properties": { - "addresses": { - "description": "The set of addresses assigned to the port configuration.", - "type": "array", - "items": { - "$ref": "#/components/schemas/Address" - } - } - }, - "required": [ - "addresses" - ] - }, "AddressLot": { "description": "Represents an address lot object, containing the id of the lot that can be used in other API calls.", "type": "object", @@ -17215,53 +16691,6 @@ "minLength": 1, "maxLength": 11 }, - "LinkConfigCreate": { - "description": "Switch link configuration.", - "type": "object", - "properties": { - "autoneg": { - "description": "Whether or not to set autonegotiation", - "type": "boolean" - }, - "fec": { - "description": "The forward error correction mode of the link.", - "allOf": [ - { - "$ref": "#/components/schemas/LinkFec" - } - ] - }, - "lldp": { - "description": "The link-layer discovery protocol (LLDP) configuration for the link.", - "allOf": [ - { - "$ref": "#/components/schemas/LldpLinkConfigCreate" - } - ] - }, - "mtu": { - "description": "Maximum transmission unit for the link.", - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "speed": { - "description": "The speed of the link.", - "allOf": [ - { - "$ref": "#/components/schemas/LinkSpeed" - } - ] - } - }, - "required": [ - "autoneg", - "fec", - "lldp", - "mtu", - "speed" - ] - }, "LinkFec": { "description": "The forward error correction mode of a link.", "oneOf": [ @@ -17356,103 +16785,6 @@ } ] }, - "LldpLinkConfig": { - "description": "A link layer discovery protocol (LLDP) service configuration.", - "type": "object", - "properties": { - "chassis_id": { - "nullable": true, - "description": "The LLDP chassis identifier TLV.", - "type": "string" - }, - "enabled": { - "description": "Whether or not the LLDP service is enabled.", - "type": "boolean" - }, - "id": { - "description": "The id of this LLDP service instance.", - "type": "string", - "format": "uuid" - }, - "link_description": { - "nullable": true, - "description": "The LLDP link description TLV.", - "type": "string" - }, - "link_name": { - "nullable": true, - "description": "The LLDP link name TLV.", - "type": "string" - }, - "management_ip": { - "nullable": true, - "description": "The LLDP management IP TLV.", - "allOf": [ - { - "$ref": "#/components/schemas/IpNet" - } - ] - }, - "system_description": { - "nullable": true, - "description": "The LLDP system description TLV.", - "type": "string" - }, - "system_name": { - "nullable": true, - "description": "The LLDP system name TLV.", - "type": "string" - } - }, - "required": [ - "enabled", - "id" - ] - }, - "LldpLinkConfigCreate": { - "description": "The LLDP configuration associated with a port.", - "type": "object", - "properties": { - "chassis_id": { - "nullable": true, - "description": "The LLDP chassis identifier TLV.", - "type": "string" - }, - "enabled": { - "description": "Whether or not LLDP is enabled.", - "type": "boolean" - }, - "link_description": { - "nullable": true, - "description": "The LLDP link description TLV.", - "type": "string" - }, - "link_name": { - "nullable": true, - "description": "The LLDP link name TLV.", - "type": "string" - }, - "management_ip": { - "nullable": true, - "description": "The LLDP management IP TLV.", - "type": "string", - "format": "ip" - }, - "system_description": { - "nullable": true, - "description": "The LLDP system description TLV.", - "type": "string" - }, - "system_name": { - "nullable": true, - "description": "The LLDP system name TLV.", - "type": "string" - } - }, - "required": [ - "enabled" - ] - }, "LoopbackAddress": { "description": "A loopback address is an address that is assigned to a rack switch but is not associated with any particular port.", "type": "object", @@ -20131,194 +19463,29 @@ "time_modified" ] }, - "SwitchBgpHistory": { - "description": "BGP message history for a particular switch.", - "type": "object", - "properties": { - "history": { - "description": "Message history indexed by peer address.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/BgpMessageHistory" - } - }, - "switch": { - "description": "Switch this message history is associated with.", - "allOf": [ - { - "$ref": "#/components/schemas/SwitchLocation" - } - ] - } - }, - "required": [ - "history", - "switch" - ] - }, - "SwitchInterfaceConfig": { - "description": "A switch port interface configuration for a port settings object.", - "type": "object", - "properties": { - "id": { - "description": "A unique identifier for this switch interface.", - "type": "string", - "format": "uuid" - }, - "interface_name": { - "description": "The name of this switch interface.", - "type": "string" - }, - "kind": { - "description": "The switch interface kind.", - "allOf": [ - { - "$ref": "#/components/schemas/SwitchInterfaceKind2" - } - ] - }, - "port_settings_id": { - "description": "The port settings object this switch interface configuration belongs to.", - "type": "string", - "format": "uuid" - }, - "v6_enabled": { - "description": "Whether or not IPv6 is enabled on this interface.", - "type": "boolean" - } - }, - "required": [ - "id", - "interface_name", - "kind", - "port_settings_id", - "v6_enabled" - ] - }, - "SwitchInterfaceConfigCreate": { - "description": "A layer-3 switch interface configuration. When IPv6 is enabled, a link local address will be created for the interface.", - "type": "object", - "properties": { - "kind": { - "description": "What kind of switch interface this configuration represents.", - "allOf": [ - { - "$ref": "#/components/schemas/SwitchInterfaceKind" - } - ] - }, - "v6_enabled": { - "description": "Whether or not IPv6 is enabled.", - "type": "boolean" - } - }, - "required": [ - "kind", - "v6_enabled" - ] - }, - "SwitchInterfaceConfigResultsPage": { - "description": "A single page of results", - "type": "object", - "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchInterfaceConfig" - } - }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" - } - }, - "required": [ - "items" - ] - }, - "SwitchInterfaceKind": { - "description": "Indicates the kind for a switch interface.", - "oneOf": [ - { - "description": "Primary interfaces are associated with physical links. There is exactly one primary interface per physical link.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "primary" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "VLAN interfaces allow physical interfaces to be multiplexed onto multiple logical links, each distinguished by a 12-bit 802.1Q Ethernet tag.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "vlan" - ] - }, - "vid": { - "description": "The virtual network id (VID) that distinguishes this interface and is used for producing and consuming 802.1Q Ethernet tags. This field has a maximum value of 4095 as 802.1Q tags are twelve bits.", - "type": "integer", - "format": "uint16", - "minimum": 0 - } - }, - "required": [ - "type", - "vid" - ] - }, - { - "description": "Loopback interfaces are anchors for IP addresses that are not specific to any particular port.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "loopback" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "SwitchInterfaceKind2": { - "description": "Describes the kind of an switch interface.", - "oneOf": [ - { - "description": "Primary interfaces are associated with physical links. There is exactly one primary interface per physical link.", - "type": "string", - "enum": [ - "primary" - ] - }, - { - "description": "VLAN interfaces allow physical interfaces to be multiplexed onto multiple logical links, each distinguished by a 12-bit 802.1Q Ethernet tag.", - "type": "string", - "enum": [ - "vlan" - ] + "SwitchBgpHistory": { + "description": "BGP message history for a particular switch.", + "type": "object", + "properties": { + "history": { + "description": "Message history indexed by peer address.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/BgpMessageHistory" + } }, - { - "description": "Loopback interfaces are anchors for IP addresses that are not specific to any particular port.", - "type": "string", - "enum": [ - "loopback" + "switch": { + "description": "Switch this message history is associated with.", + "allOf": [ + { + "$ref": "#/components/schemas/SwitchLocation" + } ] } + }, + "required": [ + "history", + "switch" ] }, "SwitchLinkState": {}, @@ -20443,7 +19610,7 @@ "description": "The physical link geometry of the port.", "allOf": [ { - "$ref": "#/components/schemas/SwitchPortGeometry2" + "$ref": "#/components/schemas/SwitchPortGeometry" } ] }, @@ -20466,7 +19633,7 @@ "description": "Link geometry for the switch port.", "allOf": [ { - "$ref": "#/components/schemas/SwitchPortGeometry" + "$ref": "#/components/schemas/SwitchPortGeometry2" } ] } @@ -20603,295 +19770,6 @@ "items" ] }, - "SwitchPortRouteConfig": { - "description": "A route configuration for a port settings object.", - "type": "object", - "properties": { - "dst": { - "description": "The route's destination network.", - "allOf": [ - { - "$ref": "#/components/schemas/IpNet" - } - ] - }, - "gw": { - "description": "The route's gateway address.", - "allOf": [ - { - "$ref": "#/components/schemas/IpNet" - } - ] - }, - "interface_name": { - "description": "The interface name this route configuration is assigned to.", - "type": "string" - }, - "local_pref": { - "nullable": true, - "description": "Local preference indicating priority within and across protocols.", - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "port_settings_id": { - "description": "The port settings object this route configuration belongs to.", - "type": "string", - "format": "uuid" - }, - "vlan_id": { - "nullable": true, - "description": "The VLAN identifier for the route. Use this if the gateway is reachable over an 802.1Q tagged L2 segment.", - "type": "integer", - "format": "uint16", - "minimum": 0 - } - }, - "required": [ - "dst", - "gw", - "interface_name", - "port_settings_id" - ] - }, - "SwitchPortSettings": { - "description": "A switch port settings identity whose id may be used to view additional details.", - "type": "object", - "properties": { - "description": { - "description": "human-readable free-form text about a resource", - "type": "string" - }, - "id": { - "description": "unique, immutable, system-controlled identifier for each resource", - "type": "string", - "format": "uuid" - }, - "name": { - "description": "unique, mutable, user-controlled identifier for each resource", - "allOf": [ - { - "$ref": "#/components/schemas/Name" - } - ] - }, - "time_created": { - "description": "timestamp when this resource was created", - "type": "string", - "format": "date-time" - }, - "time_modified": { - "description": "timestamp when this resource was last modified", - "type": "string", - "format": "date-time" - } - }, - "required": [ - "description", - "id", - "name", - "time_created", - "time_modified" - ] - }, - "SwitchPortSettingsCreate": { - "description": "Parameters for creating switch port settings. Switch port settings are the central data structure for setting up external networking. Switch port settings include link, interface, route, address and dynamic network protocol configuration.", - "type": "object", - "properties": { - "addresses": { - "description": "Addresses indexed by interface name.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/AddressConfig" - } - }, - "bgp_peers": { - "description": "BGP peers indexed by interface name.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/BgpPeerConfig" - } - }, - "description": { - "type": "string" - }, - "groups": { - "type": "array", - "items": { - "$ref": "#/components/schemas/NameOrId" - } - }, - "interfaces": { - "description": "Interfaces indexed by link name.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/SwitchInterfaceConfigCreate" - } - }, - "links": { - "description": "Links indexed by phy name. On ports that are not broken out, this is always phy0. On a 2x breakout the options are phy0 and phy1, on 4x phy0-phy3, etc.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/LinkConfigCreate" - } - }, - "name": { - "$ref": "#/components/schemas/Name" - }, - "port_config": { - "$ref": "#/components/schemas/SwitchPortConfigCreate" - }, - "routes": { - "description": "Routes indexed by interface name.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/RouteConfig" - } - } - }, - "required": [ - "addresses", - "bgp_peers", - "description", - "groups", - "interfaces", - "links", - "name", - "port_config", - "routes" - ] - }, - "SwitchPortSettingsGroups": { - "description": "This structure maps a port settings object to a port settings groups. Port settings objects may inherit settings from groups. This mapping defines the relationship between settings objects and the groups they reference.", - "type": "object", - "properties": { - "port_settings_group_id": { - "description": "The id of a port settings group being referenced by a port settings object.", - "type": "string", - "format": "uuid" - }, - "port_settings_id": { - "description": "The id of a port settings object referencing a port settings group.", - "type": "string", - "format": "uuid" - } - }, - "required": [ - "port_settings_group_id", - "port_settings_id" - ] - }, - "SwitchPortSettingsResultsPage": { - "description": "A single page of results", - "type": "object", - "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortSettings" - } - }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" - } - }, - "required": [ - "items" - ] - }, - "SwitchPortSettingsView": { - "description": "This structure contains all port settings information in one place. It's a convenience data structure for getting a complete view of a particular port's settings.", - "type": "object", - "properties": { - "addresses": { - "description": "Layer 3 IP address settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortAddressConfig" - } - }, - "bgp_peers": { - "description": "BGP peer settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/BgpPeer" - } - }, - "groups": { - "description": "Switch port settings included from other switch port settings groups.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortSettingsGroups" - } - }, - "interfaces": { - "description": "Layer 3 interface settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchInterfaceConfig" - } - }, - "link_lldp": { - "description": "Link-layer discovery protocol (LLDP) settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/LldpLinkConfig" - } - }, - "links": { - "description": "Layer 2 link settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortLinkConfig" - } - }, - "port": { - "description": "Layer 1 physical port settings.", - "allOf": [ - { - "$ref": "#/components/schemas/SwitchPortConfig" - } - ] - }, - "routes": { - "description": "IP route settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortRouteConfig" - } - }, - "settings": { - "description": "The primary switch port settings handle.", - "allOf": [ - { - "$ref": "#/components/schemas/SwitchPortSettings" - } - ] - }, - "vlan_interfaces": { - "description": "Vlan interface settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchVlanInterfaceConfig" - } - } - }, - "required": [ - "addresses", - "bgp_peers", - "groups", - "interfaces", - "link_lldp", - "links", - "port", - "routes", - "settings", - "vlan_interfaces" - ] - }, "SwitchResultsPage": { "description": "A single page of results", "type": "object", @@ -20913,27 +19791,6 @@ "items" ] }, - "SwitchVlanInterfaceConfig": { - "description": "A switch port VLAN interface configuration for a port settings object.", - "type": "object", - "properties": { - "interface_config_id": { - "description": "The switch interface configuration this VLAN interface configuration belongs to.", - "type": "string", - "format": "uuid" - }, - "vlan_id": { - "description": "The virtual network id for this interface that is used for producing and consuming 802.1Q Ethernet tags. This field has a maximum value of 4095 as 802.1Q tags are twelve bits.", - "type": "integer", - "format": "uint16", - "minimum": 0 - } - }, - "required": [ - "interface_config_id", - "vlan_id" - ] - }, "Table": { "description": "A table represents one or more timeseries with the same schema.\n\nA table is the result of an OxQL query. It contains a name, usually the name of the timeseries schema from which the data is derived, and any number of timeseries, which contain the actual data.", "type": "object", From 2e392d20262660c603849d23420833ed60dbe51a Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 29 Aug 2024 19:30:13 +0000 Subject: [PATCH 16/37] WIP: more refactor work --- nexus/src/external_api/http_entrypoints.rs | 6 + nexus/tests/integration_tests/endpoints.rs | 2 +- nexus/tests/output/nexus_tags.txt | 4 + .../output/unexpected-authz-endpoints.txt | 4 - openapi/nexus.json | 850 +++++++++++++++++- 5 files changed, 827 insertions(+), 39 deletions(-) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index e9d97ca76d2..49249807704 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -272,6 +272,12 @@ pub(crate) fn external_api() -> NexusApiDescription { // TODO: Levon - Group composition (omicron#4405)? // /v1/system/networking/switch-port-configuration-group/{name_or_id}/.. + // /v1/system/networking/switch-port-configuration + api.register(networking_switch_port_configuration_list)?; + api.register(networking_switch_port_configuration_view)?; + api.register(networking_switch_port_configuration_create)?; + api.register(networking_switch_port_configuration_delete)?; + // /v1/system/networking/switch-port-configuration/{name_or_id}/geometry // TODO: Levon - test api.register(networking_switch_port_configuration_geometry_view)?; diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 2829131a1d6..fb42577e95e 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -529,7 +529,7 @@ pub static DEMO_LOOPBACK_CREATE: Lazy = pub const DEMO_SWITCH_PORT_SETTINGS_URL: &'static str = "/v1/system/networking/switch-port-configuration?port_settings=portofino"; pub const DEMO_SWITCH_PORT_SETTINGS_INFO_URL: &'static str = - "/v1/system/networking/switch-port-configuration/protofino"; + "/v1/system/networking/switch-port-configuration/portofino"; pub static DEMO_SWITCH_PORT_SETTINGS_CREATE: Lazy< params::SwitchPortSettingsCreate, > = Lazy::new(|| { diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 8cd73db3ea4..87cbead33c0 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -194,6 +194,8 @@ networking_bgp_status GET /v1/system/networking/bgp-stat networking_loopback_address_create POST /v1/system/networking/loopback-address networking_loopback_address_delete DELETE /v1/system/networking/loopback-address/{rack_id}/{switch_location}/{address}/{subnet_mask} networking_loopback_address_list GET /v1/system/networking/loopback-address +networking_switch_port_configuration_create POST /v1/system/networking/switch-port-configuration +networking_switch_port_configuration_delete DELETE /v1/system/networking/switch-port-configuration networking_switch_port_configuration_geometry_set POST /v1/system/networking/switch-port-configuration/{configuration}/geometry networking_switch_port_configuration_geometry_view GET /v1/system/networking/switch-port-configuration/{configuration}/geometry networking_switch_port_configuration_interface_address_add POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/add @@ -209,6 +211,8 @@ networking_switch_port_configuration_link_create POST /v1/system/networking/ networking_switch_port_configuration_link_delete DELETE /v1/system/networking/switch-port-configuration/{configuration}/link/{link} networking_switch_port_configuration_link_list GET /v1/system/networking/switch-port-configuration/{configuration}/link networking_switch_port_configuration_link_view GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link} +networking_switch_port_configuration_list GET /v1/system/networking/switch-port-configuration +networking_switch_port_configuration_view GET /v1/system/networking/switch-port-configuration/{configuration} API operations found with tag "system/silos" OPERATION ID METHOD URL PATH diff --git a/nexus/tests/output/unexpected-authz-endpoints.txt b/nexus/tests/output/unexpected-authz-endpoints.txt index 42c8cea15a4..23235ecf641 100644 --- a/nexus/tests/output/unexpected-authz-endpoints.txt +++ b/nexus/tests/output/unexpected-authz-endpoints.txt @@ -2,7 +2,3 @@ API endpoints tested by unauthorized.rs but not found in the OpenAPI spec: PUT "/v1/system/update/repository?file_name=demo-repo.zip" GET "/v1/system/update/repository/1.0.0" DELETE "/v1/system/networking/address-lot/parkinglot" -POST "/v1/system/networking/switch-port-configuration?port_settings=portofino" -GET "/v1/system/networking/switch-port-configuration?port_settings=portofino" -DELETE "/v1/system/networking/switch-port-configuration?port_settings=portofino" -GET "/v1/system/networking/switch-port-configuration/protofino" diff --git a/openapi/nexus.json b/openapi/nexus.json index 3f30e820d4d..55cfc4bcc5c 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7100,6 +7100,174 @@ } } }, + "/v1/system/networking/switch-port-configuration": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "List switch port settings", + "operationId": "networking_switch_port_configuration_list", + "parameters": [ + { + "in": "query", + "name": "configuration", + "description": "An optional name or id to use when selecting a switch port configuration.", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettingsResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "tags": [ + "system/networking" + ], + "summary": "Create switch port settings", + "operationId": "networking_switch_port_configuration_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettingsCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettingsView" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "system/networking" + ], + "summary": "Delete switch port settings", + "operationId": "networking_switch_port_configuration_delete", + "parameters": [ + { + "in": "query", + "name": "configuration", + "description": "An optional name or id to use when selecting a switch port configuration.", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "Get information about a named set of switch-port-settings", + "operationId": "networking_switch_port_configuration_view", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettingsView" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/system/networking/switch-port-configuration/{configuration}/geometry": { "get": { "tags": [ @@ -10322,6 +10490,22 @@ "address_lot" ] }, + "AddressConfig": { + "description": "A set of addresses associated with a port configuration.", + "type": "object", + "properties": { + "addresses": { + "description": "The set of addresses assigned to the port configuration.", + "type": "array", + "items": { + "$ref": "#/components/schemas/Address" + } + } + }, + "required": [ + "addresses" + ] + }, "AddressLot": { "description": "Represents an address lot object, containing the id of the lot that can be used in other API calls.", "type": "object", @@ -16691,6 +16875,53 @@ "minLength": 1, "maxLength": 11 }, + "LinkConfigCreate": { + "description": "Switch link configuration.", + "type": "object", + "properties": { + "autoneg": { + "description": "Whether or not to set autonegotiation", + "type": "boolean" + }, + "fec": { + "description": "The forward error correction mode of the link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkFec" + } + ] + }, + "lldp": { + "description": "The link-layer discovery protocol (LLDP) configuration for the link.", + "allOf": [ + { + "$ref": "#/components/schemas/LldpLinkConfigCreate" + } + ] + }, + "mtu": { + "description": "Maximum transmission unit for the link.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "speed": { + "description": "The speed of the link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkSpeed" + } + ] + } + }, + "required": [ + "autoneg", + "fec", + "lldp", + "mtu", + "speed" + ] + }, "LinkFec": { "description": "The forward error correction mode of a link.", "oneOf": [ @@ -16785,55 +17016,152 @@ } ] }, - "LoopbackAddress": { - "description": "A loopback address is an address that is assigned to a rack switch but is not associated with any particular port.", + "LldpLinkConfig": { + "description": "A link layer discovery protocol (LLDP) service configuration.", "type": "object", "properties": { - "address": { - "description": "The loopback IP address and prefix length.", - "allOf": [ - { - "$ref": "#/components/schemas/IpNet" - } - ] + "chassis_id": { + "nullable": true, + "description": "The LLDP chassis identifier TLV.", + "type": "string" }, - "address_lot_block_id": { - "description": "The address lot block this address came from.", - "type": "string", - "format": "uuid" + "enabled": { + "description": "Whether or not the LLDP service is enabled.", + "type": "boolean" }, "id": { - "description": "The id of the loopback address.", + "description": "The id of this LLDP service instance.", "type": "string", "format": "uuid" }, - "rack_id": { - "description": "The id of the rack where this loopback address is assigned.", - "type": "string", - "format": "uuid" + "link_description": { + "nullable": true, + "description": "The LLDP link description TLV.", + "type": "string" }, - "switch_location": { - "description": "Switch location where this loopback address is assigned.", + "link_name": { + "nullable": true, + "description": "The LLDP link name TLV.", + "type": "string" + }, + "management_ip": { + "nullable": true, + "description": "The LLDP management IP TLV.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "system_description": { + "nullable": true, + "description": "The LLDP system description TLV.", + "type": "string" + }, + "system_name": { + "nullable": true, + "description": "The LLDP system name TLV.", "type": "string" } }, "required": [ - "address", - "address_lot_block_id", - "id", - "rack_id", - "switch_location" + "enabled", + "id" ] }, - "LoopbackAddressCreate": { - "description": "Parameters for creating a loopback address on a particular rack switch.", + "LldpLinkConfigCreate": { + "description": "The LLDP configuration associated with a port.", "type": "object", "properties": { - "address": { - "description": "The address to create.", - "type": "string", - "format": "ip" - }, + "chassis_id": { + "nullable": true, + "description": "The LLDP chassis identifier TLV.", + "type": "string" + }, + "enabled": { + "description": "Whether or not LLDP is enabled.", + "type": "boolean" + }, + "link_description": { + "nullable": true, + "description": "The LLDP link description TLV.", + "type": "string" + }, + "link_name": { + "nullable": true, + "description": "The LLDP link name TLV.", + "type": "string" + }, + "management_ip": { + "nullable": true, + "description": "The LLDP management IP TLV.", + "type": "string", + "format": "ip" + }, + "system_description": { + "nullable": true, + "description": "The LLDP system description TLV.", + "type": "string" + }, + "system_name": { + "nullable": true, + "description": "The LLDP system name TLV.", + "type": "string" + } + }, + "required": [ + "enabled" + ] + }, + "LoopbackAddress": { + "description": "A loopback address is an address that is assigned to a rack switch but is not associated with any particular port.", + "type": "object", + "properties": { + "address": { + "description": "The loopback IP address and prefix length.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "address_lot_block_id": { + "description": "The address lot block this address came from.", + "type": "string", + "format": "uuid" + }, + "id": { + "description": "The id of the loopback address.", + "type": "string", + "format": "uuid" + }, + "rack_id": { + "description": "The id of the rack where this loopback address is assigned.", + "type": "string", + "format": "uuid" + }, + "switch_location": { + "description": "Switch location where this loopback address is assigned.", + "type": "string" + } + }, + "required": [ + "address", + "address_lot_block_id", + "id", + "rack_id", + "switch_location" + ] + }, + "LoopbackAddressCreate": { + "description": "Parameters for creating a loopback address on a particular rack switch.", + "type": "object", + "properties": { + "address": { + "description": "The address to create.", + "type": "string", + "format": "ip" + }, "address_lot": { "description": "The name or id of the address lot this loopback address will pull an address from.", "allOf": [ @@ -19488,6 +19816,150 @@ "switch" ] }, + "SwitchInterfaceConfig": { + "description": "A switch port interface configuration for a port settings object.", + "type": "object", + "properties": { + "id": { + "description": "A unique identifier for this switch interface.", + "type": "string", + "format": "uuid" + }, + "interface_name": { + "description": "The name of this switch interface.", + "type": "string" + }, + "kind": { + "description": "The switch interface kind.", + "allOf": [ + { + "$ref": "#/components/schemas/SwitchInterfaceKind2" + } + ] + }, + "port_settings_id": { + "description": "The port settings object this switch interface configuration belongs to.", + "type": "string", + "format": "uuid" + }, + "v6_enabled": { + "description": "Whether or not IPv6 is enabled on this interface.", + "type": "boolean" + } + }, + "required": [ + "id", + "interface_name", + "kind", + "port_settings_id", + "v6_enabled" + ] + }, + "SwitchInterfaceConfigCreate": { + "description": "A layer-3 switch interface configuration. When IPv6 is enabled, a link local address will be created for the interface.", + "type": "object", + "properties": { + "kind": { + "description": "What kind of switch interface this configuration represents.", + "allOf": [ + { + "$ref": "#/components/schemas/SwitchInterfaceKind" + } + ] + }, + "v6_enabled": { + "description": "Whether or not IPv6 is enabled.", + "type": "boolean" + } + }, + "required": [ + "kind", + "v6_enabled" + ] + }, + "SwitchInterfaceKind": { + "description": "Indicates the kind for a switch interface.", + "oneOf": [ + { + "description": "Primary interfaces are associated with physical links. There is exactly one primary interface per physical link.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "primary" + ] + } + }, + "required": [ + "type" + ] + }, + { + "description": "VLAN interfaces allow physical interfaces to be multiplexed onto multiple logical links, each distinguished by a 12-bit 802.1Q Ethernet tag.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "vlan" + ] + }, + "vid": { + "description": "The virtual network id (VID) that distinguishes this interface and is used for producing and consuming 802.1Q Ethernet tags. This field has a maximum value of 4095 as 802.1Q tags are twelve bits.", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "type", + "vid" + ] + }, + { + "description": "Loopback interfaces are anchors for IP addresses that are not specific to any particular port.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "loopback" + ] + } + }, + "required": [ + "type" + ] + } + ] + }, + "SwitchInterfaceKind2": { + "description": "Describes the kind of an switch interface.", + "oneOf": [ + { + "description": "Primary interfaces are associated with physical links. There is exactly one primary interface per physical link.", + "type": "string", + "enum": [ + "primary" + ] + }, + { + "description": "VLAN interfaces allow physical interfaces to be multiplexed onto multiple logical links, each distinguished by a 12-bit 802.1Q Ethernet tag.", + "type": "string", + "enum": [ + "vlan" + ] + }, + { + "description": "Loopback interfaces are anchors for IP addresses that are not specific to any particular port.", + "type": "string", + "enum": [ + "loopback" + ] + } + ] + }, "SwitchLinkState": {}, "SwitchLocation": { "description": "Identifies switch physical location", @@ -19610,7 +20082,7 @@ "description": "The physical link geometry of the port.", "allOf": [ { - "$ref": "#/components/schemas/SwitchPortGeometry" + "$ref": "#/components/schemas/SwitchPortGeometry2" } ] }, @@ -19633,7 +20105,7 @@ "description": "Link geometry for the switch port.", "allOf": [ { - "$ref": "#/components/schemas/SwitchPortGeometry2" + "$ref": "#/components/schemas/SwitchPortGeometry" } ] } @@ -19770,6 +20242,295 @@ "items" ] }, + "SwitchPortRouteConfig": { + "description": "A route configuration for a port settings object.", + "type": "object", + "properties": { + "dst": { + "description": "The route's destination network.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "gw": { + "description": "The route's gateway address.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "interface_name": { + "description": "The interface name this route configuration is assigned to.", + "type": "string" + }, + "local_pref": { + "nullable": true, + "description": "Local preference indicating priority within and across protocols.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "port_settings_id": { + "description": "The port settings object this route configuration belongs to.", + "type": "string", + "format": "uuid" + }, + "vlan_id": { + "nullable": true, + "description": "The VLAN identifier for the route. Use this if the gateway is reachable over an 802.1Q tagged L2 segment.", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "dst", + "gw", + "interface_name", + "port_settings_id" + ] + }, + "SwitchPortSettings": { + "description": "A switch port settings identity whose id may be used to view additional details.", + "type": "object", + "properties": { + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" + }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "description", + "id", + "name", + "time_created", + "time_modified" + ] + }, + "SwitchPortSettingsCreate": { + "description": "Parameters for creating switch port settings. Switch port settings are the central data structure for setting up external networking. Switch port settings include link, interface, route, address and dynamic network protocol configuration.", + "type": "object", + "properties": { + "addresses": { + "description": "Addresses indexed by interface name.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/AddressConfig" + } + }, + "bgp_peers": { + "description": "BGP peers indexed by interface name.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/BgpPeerConfig" + } + }, + "description": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameOrId" + } + }, + "interfaces": { + "description": "Interfaces indexed by link name.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/SwitchInterfaceConfigCreate" + } + }, + "links": { + "description": "Links indexed by phy name. On ports that are not broken out, this is always phy0. On a 2x breakout the options are phy0 and phy1, on 4x phy0-phy3, etc.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/LinkConfigCreate" + } + }, + "name": { + "$ref": "#/components/schemas/Name" + }, + "port_config": { + "$ref": "#/components/schemas/SwitchPortConfigCreate" + }, + "routes": { + "description": "Routes indexed by interface name.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/RouteConfig" + } + } + }, + "required": [ + "addresses", + "bgp_peers", + "description", + "groups", + "interfaces", + "links", + "name", + "port_config", + "routes" + ] + }, + "SwitchPortSettingsGroups": { + "description": "This structure maps a port settings object to a port settings groups. Port settings objects may inherit settings from groups. This mapping defines the relationship between settings objects and the groups they reference.", + "type": "object", + "properties": { + "port_settings_group_id": { + "description": "The id of a port settings group being referenced by a port settings object.", + "type": "string", + "format": "uuid" + }, + "port_settings_id": { + "description": "The id of a port settings object referencing a port settings group.", + "type": "string", + "format": "uuid" + } + }, + "required": [ + "port_settings_group_id", + "port_settings_id" + ] + }, + "SwitchPortSettingsResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortSettings" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "SwitchPortSettingsView": { + "description": "This structure contains all port settings information in one place. It's a convenience data structure for getting a complete view of a particular port's settings.", + "type": "object", + "properties": { + "addresses": { + "description": "Layer 3 IP address settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortAddressConfig" + } + }, + "bgp_peers": { + "description": "BGP peer settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpPeer" + } + }, + "groups": { + "description": "Switch port settings included from other switch port settings groups.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortSettingsGroups" + } + }, + "interfaces": { + "description": "Layer 3 interface settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchInterfaceConfig" + } + }, + "link_lldp": { + "description": "Link-layer discovery protocol (LLDP) settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/LldpLinkConfig" + } + }, + "links": { + "description": "Layer 2 link settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortLinkConfig" + } + }, + "port": { + "description": "Layer 1 physical port settings.", + "allOf": [ + { + "$ref": "#/components/schemas/SwitchPortConfig" + } + ] + }, + "routes": { + "description": "IP route settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortRouteConfig" + } + }, + "settings": { + "description": "The primary switch port settings handle.", + "allOf": [ + { + "$ref": "#/components/schemas/SwitchPortSettings" + } + ] + }, + "vlan_interfaces": { + "description": "Vlan interface settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchVlanInterfaceConfig" + } + } + }, + "required": [ + "addresses", + "bgp_peers", + "groups", + "interfaces", + "link_lldp", + "links", + "port", + "routes", + "settings", + "vlan_interfaces" + ] + }, "SwitchResultsPage": { "description": "A single page of results", "type": "object", @@ -19791,6 +20552,27 @@ "items" ] }, + "SwitchVlanInterfaceConfig": { + "description": "A switch port VLAN interface configuration for a port settings object.", + "type": "object", + "properties": { + "interface_config_id": { + "description": "The switch interface configuration this VLAN interface configuration belongs to.", + "type": "string", + "format": "uuid" + }, + "vlan_id": { + "description": "The virtual network id for this interface that is used for producing and consuming 802.1Q Ethernet tags. This field has a maximum value of 4095 as 802.1Q tags are twelve bits.", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "interface_config_id", + "vlan_id" + ] + }, "Table": { "description": "A table represents one or more timeseries with the same schema.\n\nA table is the result of an OxQL query. It contains a name, usually the name of the timeseries schema from which the data is derived, and any number of timeseries, which contain the actual data.", "type": "object", From 8cad50ec47597c41a33910ff75fc766ba26e4828 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 29 Aug 2024 20:55:18 +0000 Subject: [PATCH 17/37] Make addresses list-able without knowing interface name --- .../src/db/datastore/switch_port.rs | 22 +++---- nexus/src/app/switch_port.rs | 25 +++----- nexus/src/external_api/http_entrypoints.rs | 61 ++++++------------- nexus/types/src/external_api/params.rs | 16 +++++ 4 files changed, 52 insertions(+), 72 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 32f6b68c28f..51feecf575c 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -899,11 +899,10 @@ impl DataStore { Ok(()) } - pub async fn switch_port_configuration_interface_address_list( + pub async fn switch_port_configuration_address_list( &self, opctx: &OpContext, configuration: NameOrId, - interface: Name, ) -> ListResultVec { use db::schema::switch_port_settings as port_settings; use db::schema::switch_port_settings_address_config as address_config; @@ -929,7 +928,6 @@ impl DataStore { let configs: Vec = query .select(SwitchPortAddressConfig::as_select()) - .filter(address_config::interface_name.eq(interface)) .load_async(&*self.pool_connection_authorized(opctx).await?) .await .map_err(|e: diesel::result::Error| { @@ -947,12 +945,11 @@ impl DataStore { Ok(configs) } - pub async fn switch_port_configuration_interface_address_add( + pub async fn switch_port_configuration_address_add( &self, opctx: &OpContext, configuration: NameOrId, - interface: Name, - address: params::Address, + address: params::AddressAddRemove, ) -> CreateResult { use db::schema::address_lot; use db::schema::switch_port_settings_address_config as address_config; @@ -965,7 +962,6 @@ impl DataStore { ) .transaction(&conn, |conn| { let parent_configuration = configuration.clone(); - let interface = interface.clone(); let new_settings = address.clone(); let err = err.clone(); @@ -1071,7 +1067,7 @@ impl DataStore { address_lot_block_id: block.id, rsvd_address_lot_block_id: rsvd_block.id, address: new_settings.address.into(), - interface_name: interface.to_string(), + interface_name: new_settings.interface.to_string(), vlan_id: new_settings.vlan_id.map(|i| i.into()), }; @@ -1102,12 +1098,11 @@ impl DataStore { }) } - pub async fn switch_port_configuration_interface_address_remove( + pub async fn switch_port_configuration_address_remove( &self, opctx: &OpContext, configuration: NameOrId, - interface: Name, - address: params::Address, + address: params::AddressAddRemove, ) -> DeleteResult { use db::schema::address_lot; use db::schema::switch_port_settings_address_config as address_config; @@ -1120,7 +1115,6 @@ impl DataStore { ) .transaction(&conn, |conn| { let parent_configuration = configuration.clone(); - let interface = interface.clone(); let settings_to_remove = address.clone(); let err = err.clone(); @@ -1200,7 +1194,7 @@ impl DataStore { .filter(address_config::address.eq(IpNetwork::from(settings_to_remove.address))) .filter(address_config::port_settings_id.eq(port_settings_id)) .filter(address_config::address_lot_block_id.eq(address_lot_id)) - .filter(address_config::interface_name.eq(interface.clone())) + .filter(address_config::interface_name.eq(settings_to_remove.interface.clone().to_string())) .select(SwitchPortAddressConfig::as_select()) .limit(1) .first_async::(&conn) @@ -1230,7 +1224,7 @@ impl DataStore { .filter(address_config::address.eq(IpNetwork::from(settings_to_remove.address))) .filter(address_config::port_settings_id.eq(port_settings_id)) .filter(address_config::address_lot_block_id.eq(address_lot_id)) - .filter(address_config::interface_name.eq(interface)) + .filter(address_config::interface_name.eq(settings_to_remove.interface.to_string())) .execute_async(&conn) .await?; diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 5b4a4241549..38540c6e3cf 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -209,53 +209,44 @@ impl super::Nexus { .await } - pub(crate) async fn switch_port_configuration_interface_address_list( + pub(crate) async fn switch_port_configuration_address_list( &self, opctx: &OpContext, configuration: NameOrId, - interface: Name, ) -> ListResultVec { opctx.authorize(authz::Action::Read, &authz::FLEET).await?; self.db_datastore - .switch_port_configuration_interface_address_list( - opctx, - configuration, - interface.into(), - ) + .switch_port_configuration_address_list(opctx, configuration) .await } - pub(crate) async fn switch_port_configuration_interface_address_add( + pub(crate) async fn switch_port_configuration_address_add( &self, opctx: &OpContext, configuration: NameOrId, - interface: Name, - address: params::Address, + address: params::AddressAddRemove, ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; self.db_datastore - .switch_port_configuration_interface_address_add( + .switch_port_configuration_address_add( opctx, configuration, - interface.into(), address, ) .await } - pub(crate) async fn switch_port_configuration_interface_address_remove( + pub(crate) async fn switch_port_configuration_address_remove( &self, opctx: &OpContext, configuration: NameOrId, - interface: Name, - address: params::Address, + address: params::AddressAddRemove, ) -> DeleteResult { opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; self.db_datastore - .switch_port_configuration_interface_address_remove( + .switch_port_configuration_address_remove( opctx, configuration, - interface.into(), address, ) .await diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 49249807704..c33ff88362a 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -296,17 +296,11 @@ pub(crate) fn external_api() -> NexusApiDescription { // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/address // TODO: Levon - test - api.register( - networking_switch_port_configuration_interface_address_add, - )?; + api.register(networking_switch_port_configuration_address_add)?; // TODO: Levon - test - api.register( - networking_switch_port_configuration_interface_address_remove, - )?; + api.register(networking_switch_port_configuration_address_remove)?; // TODO: Levon - test - api.register( - networking_switch_port_configuration_interface_address_list, - )?; + api.register(networking_switch_port_configuration_address_list)?; // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/route // TODO: Levon - test @@ -4055,28 +4049,21 @@ async fn networking_switch_port_configuration_link_delete( /// List addresses assigned to a provided interface configuration #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address", + path ="/v1/system/networking/switch-port-configuration/{configuration}/address", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_interface_address_list( +async fn networking_switch_port_configuration_address_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsInterfaceInfoSelector { - configuration, - interface, - } = path_params.into_inner(); + let configuration = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus - .switch_port_configuration_interface_address_list( - &opctx, - configuration, - interface, - ) + .switch_port_configuration_address_list(&opctx, configuration) .await?; Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) }; @@ -4090,29 +4077,25 @@ async fn networking_switch_port_configuration_interface_address_list( /// Add address to an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/add", + path ="/v1/system/networking/switch-port-configuration/{configuration}/address/add", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_interface_address_add( +async fn networking_switch_port_configuration_address_add( rqctx: RequestContext, - path_params: Path, - address: TypedBody, + path_params: Path, + address: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsInterfaceInfoSelector { - configuration, - interface, - } = path_params.into_inner(); + let configuration = path_params.into_inner().configuration; let address = address.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus - .switch_port_configuration_interface_address_add( + .switch_port_configuration_address_add( &opctx, configuration, - interface, address, ) .await?; @@ -4128,29 +4111,25 @@ async fn networking_switch_port_configuration_interface_address_add( /// Remove address from an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/remove", + path ="/v1/system/networking/switch-port-configuration/{configuration}/address/remove", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_interface_address_remove( +async fn networking_switch_port_configuration_address_remove( rqctx: RequestContext, - path_params: Path, - address: TypedBody, + path_params: Path, + address: TypedBody, ) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsInterfaceInfoSelector { - configuration, - interface, - } = path_params.into_inner(); + let configuration = path_params.into_inner().configuration; let address = address.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; nexus - .switch_port_configuration_interface_address_remove( + .switch_port_configuration_address_remove( &opctx, configuration, - interface, address, ) .await?; diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 742d8ae0f74..a4a66dc790e 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1775,6 +1775,22 @@ pub struct Address { pub vlan_id: Option, } +/// An address to be added or removed from an interface +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AddressAddRemove { + /// The name of the interface + pub interface: Name, + + /// The address lot this address is drawn from. + pub address_lot: NameOrId, + + /// The address and prefix length of this address. + pub address: IpNet, + + /// Optional VLAN ID for this address + pub vlan_id: Option, +} + /// Select a port settings object by an optional name or id. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct SwitchPortSettingsSelector { From 7a32cbd197ac30d5193e9aa38c75153973b1abf0 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 29 Aug 2024 21:14:05 +0000 Subject: [PATCH 18/37] regen openapi --- nexus/tests/output/nexus_tags.txt | 6 +- .../output/uncovered-authz-endpoints.txt | 6 +- openapi/nexus.json | 169 ++++++++++-------- 3 files changed, 98 insertions(+), 83 deletions(-) diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 87cbead33c0..435719b4654 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -194,13 +194,13 @@ networking_bgp_status GET /v1/system/networking/bgp-stat networking_loopback_address_create POST /v1/system/networking/loopback-address networking_loopback_address_delete DELETE /v1/system/networking/loopback-address/{rack_id}/{switch_location}/{address}/{subnet_mask} networking_loopback_address_list GET /v1/system/networking/loopback-address +networking_switch_port_configuration_address_add POST /v1/system/networking/switch-port-configuration/{configuration}/address/add +networking_switch_port_configuration_address_list GET /v1/system/networking/switch-port-configuration/{configuration}/address +networking_switch_port_configuration_address_remove POST /v1/system/networking/switch-port-configuration/{configuration}/address/remove networking_switch_port_configuration_create POST /v1/system/networking/switch-port-configuration networking_switch_port_configuration_delete DELETE /v1/system/networking/switch-port-configuration networking_switch_port_configuration_geometry_set POST /v1/system/networking/switch-port-configuration/{configuration}/geometry networking_switch_port_configuration_geometry_view GET /v1/system/networking/switch-port-configuration/{configuration}/geometry -networking_switch_port_configuration_interface_address_add POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/add -networking_switch_port_configuration_interface_address_list GET /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address -networking_switch_port_configuration_interface_address_remove POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/remove networking_switch_port_configuration_interface_bgp_peer_add POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add networking_switch_port_configuration_interface_bgp_peer_list GET /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer networking_switch_port_configuration_interface_bgp_peer_remove POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index aaab1f0401f..fa488d66857 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -5,8 +5,8 @@ probe_list (get "/experimental/v1/probes") probe_view (get "/experimental/v1/probes/{probe}") ping (get "/v1/ping") networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status") +networking_switch_port_configuration_address_list (get "/v1/system/networking/switch-port-configuration/{configuration}/address") networking_switch_port_configuration_geometry_view (get "/v1/system/networking/switch-port-configuration/{configuration}/geometry") -networking_switch_port_configuration_interface_address_list (get "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address") networking_switch_port_configuration_interface_bgp_peer_list (get "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer") networking_switch_port_configuration_interface_route_list (get "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route") networking_switch_port_configuration_link_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link") @@ -18,9 +18,9 @@ probe_create (post "/experimental/v1/probes") login_saml (post "/login/{silo_name}/saml/{provider_name}") login_local (post "/v1/login/{silo_name}/local") logout (post "/v1/logout") +networking_switch_port_configuration_address_add (post "/v1/system/networking/switch-port-configuration/{configuration}/address/add") +networking_switch_port_configuration_address_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/address/remove") networking_switch_port_configuration_geometry_set (post "/v1/system/networking/switch-port-configuration/{configuration}/geometry") -networking_switch_port_configuration_interface_address_add (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/add") -networking_switch_port_configuration_interface_address_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/remove") networking_switch_port_configuration_interface_bgp_peer_add (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add") networking_switch_port_configuration_interface_bgp_peer_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove") networking_switch_port_configuration_interface_route_add (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/add") diff --git a/openapi/nexus.json b/openapi/nexus.json index 55cfc4bcc5c..821c9277ad7 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7268,13 +7268,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/geometry": { + "/v1/system/networking/switch-port-configuration/{configuration}/address": { "get": { "tags": [ "system/networking" ], - "summary": "Get switch port geometry for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_geometry_view", + "summary": "List addresses assigned to a provided interface configuration", + "operationId": "networking_switch_port_configuration_address_list", "parameters": [ { "in": "path", @@ -7292,7 +7292,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortConfig" + "title": "Array_of_SwitchPortAddressConfig", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortAddressConfig" + } } } } @@ -7304,13 +7308,15 @@ "$ref": "#/components/responses/Error" } } - }, + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/address/add": { "post": { "tags": [ "system/networking" ], - "summary": "Set switch port geometry for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_geometry_set", + "summary": "Add address to an interface configuration", + "operationId": "networking_switch_port_configuration_address_add", "parameters": [ { "in": "path", @@ -7326,7 +7332,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortConfigCreate" + "$ref": "#/components/schemas/AddressAddRemove" } } }, @@ -7338,7 +7344,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortConfig" + "$ref": "#/components/schemas/SwitchPortAddressConfig" } } } @@ -7352,13 +7358,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address": { - "get": { + "/v1/system/networking/switch-port-configuration/{configuration}/address/remove": { + "post": { "tags": [ "system/networking" ], - "summary": "List addresses assigned to a provided interface configuration", - "operationId": "networking_switch_port_configuration_interface_address_list", + "summary": "Remove address from an interface configuration", + "operationId": "networking_switch_port_configuration_address_remove", "parameters": [ { "in": "path", @@ -7368,32 +7374,22 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "title": "Array_of_SwitchPortAddressConfig", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortAddressConfig" - } - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressAddRemove" } } }, + "required": true + }, + "responses": { + "204": { + "description": "successful deletion" + }, "4XX": { "$ref": "#/components/responses/Error" }, @@ -7403,13 +7399,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/add": { - "post": { + "/v1/system/networking/switch-port-configuration/{configuration}/geometry": { + "get": { "tags": [ "system/networking" ], - "summary": "Add address to an interface configuration", - "operationId": "networking_switch_port_configuration_interface_address_add", + "summary": "Get switch port geometry for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_geometry_view", "parameters": [ { "in": "path", @@ -7419,34 +7415,15 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Address" - } - } - }, - "required": true - }, "responses": { - "201": { - "description": "successful creation", + "200": { + "description": "successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortAddressConfig" + "$ref": "#/components/schemas/SwitchPortConfig" } } } @@ -7458,15 +7435,13 @@ "$ref": "#/components/responses/Error" } } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/address/remove": { + }, "post": { "tags": [ "system/networking" ], - "summary": "Remove address from an interface configuration", - "operationId": "networking_switch_port_configuration_interface_address_remove", + "summary": "Set switch port geometry for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_geometry_set", "parameters": [ { "in": "path", @@ -7476,30 +7451,28 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Address" + "$ref": "#/components/schemas/SwitchPortConfigCreate" } } }, "required": true }, "responses": { - "204": { - "description": "successful deletion" + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortConfig" + } + } + } }, "4XX": { "$ref": "#/components/responses/Error" @@ -10490,6 +10463,48 @@ "address_lot" ] }, + "AddressAddRemove": { + "description": "An address to be added or removed from an interface", + "type": "object", + "properties": { + "address": { + "description": "The address and prefix length of this address.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "address_lot": { + "description": "The address lot this address is drawn from.", + "allOf": [ + { + "$ref": "#/components/schemas/NameOrId" + } + ] + }, + "interface": { + "description": "The name of the interface", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "vlan_id": { + "nullable": true, + "description": "Optional VLAN ID for this address", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "address", + "address_lot", + "interface" + ] + }, "AddressConfig": { "description": "A set of addresses associated with a port configuration.", "type": "object", From 2377850481a715686298231f621c8b7ee1fcc983 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 30 Aug 2024 00:51:47 +0000 Subject: [PATCH 19/37] fix address remove logic --- .../src/db/datastore/switch_port.rs | 52 --- nexus/src/external_api/http_entrypoints.rs | 58 ++-- nexus/tests/output/nexus_tags.txt | 12 +- .../output/uncovered-authz-endpoints.txt | 12 +- openapi/nexus.json | 306 ++++++++---------- 5 files changed, 162 insertions(+), 278 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 51feecf575c..fd7b231ba52 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -1104,7 +1104,6 @@ impl DataStore { configuration: NameOrId, address: params::AddressAddRemove, ) -> DeleteResult { - use db::schema::address_lot; use db::schema::switch_port_settings_address_config as address_config; let conn = self.pool_connection_authorized(opctx).await?; @@ -1140,60 +1139,10 @@ impl DataStore { } })?; - // resolve id of referenced address lot - let address_lot_id = match settings_to_remove.address_lot { - - NameOrId::Id(id) => { - // verify id is valid - address_lot::table - .filter(address_lot::time_deleted.is_null()) - .filter(address_lot::id.eq(id)) - .select(address_lot::id) - .limit(1) - .first_async::(&conn) - .await - .map_err(|e: diesel::result::Error| { - match e { - diesel::result::Error::NotFound => { - err.bail(Error::not_found_by_id(ResourceType::AddressLot, &id)) - }, - _ => { - let message = "error while looking up address lot for interface address"; - error!(opctx.log, "{message}"; "error" => ?e); - err.bail(Error::internal_error(message)) - }, - } - }) - }, - - NameOrId::Name(name) => { - address_lot::table - .filter(address_lot::time_deleted.is_null()) - .filter(address_lot::name.eq(name.to_string())) - .select(address_lot::id) - .limit(1) - .first_async::(&conn) - .await - .map_err(|e: diesel::result::Error| { - match e { - diesel::result::Error::NotFound => { - err.bail(Error::not_found_by_name(ResourceType::AddressLot, &name)) - }, - _ => { - let message = "error while looking up address lot for interface address"; - error!(opctx.log, "{message}"; "error" => ?e); - err.bail(Error::internal_error(message)) - }, - } - }) - } - }?; - // find address config let found_address_config = address_config::table .filter(address_config::address.eq(IpNetwork::from(settings_to_remove.address))) .filter(address_config::port_settings_id.eq(port_settings_id)) - .filter(address_config::address_lot_block_id.eq(address_lot_id)) .filter(address_config::interface_name.eq(settings_to_remove.interface.clone().to_string())) .select(SwitchPortAddressConfig::as_select()) .limit(1) @@ -1223,7 +1172,6 @@ impl DataStore { diesel::delete(address_config::table) .filter(address_config::address.eq(IpNetwork::from(settings_to_remove.address))) .filter(address_config::port_settings_id.eq(port_settings_id)) - .filter(address_config::address_lot_block_id.eq(address_lot_id)) .filter(address_config::interface_name.eq(settings_to_remove.interface.to_string())) .execute_async(&conn) .await?; diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index c33ff88362a..4f495a3dad2 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -304,29 +304,19 @@ pub(crate) fn external_api() -> NexusApiDescription { // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/route // TODO: Levon - test - api.register(networking_switch_port_configuration_interface_route_add)?; + api.register(networking_switch_port_configuration_route_add)?; // TODO: Levon - test - api.register( - networking_switch_port_configuration_interface_route_remove, - )?; + api.register(networking_switch_port_configuration_route_remove)?; // TODO: Levon - test - api.register( - networking_switch_port_configuration_interface_route_list, - )?; + api.register(networking_switch_port_configuration_route_list)?; // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/bgp-peer // TODO: Levon - test - api.register( - networking_switch_port_configuration_interface_bgp_peer_add, - )?; + api.register(networking_switch_port_configuration_bgp_peer_add)?; // TODO: Levon - test - api.register( - networking_switch_port_configuration_interface_bgp_peer_remove, - )?; + api.register(networking_switch_port_configuration_bgp_peer_remove)?; // TODO: Levon - test - api.register( - networking_switch_port_configuration_interface_bgp_peer_list, - )?; + api.register(networking_switch_port_configuration_bgp_peer_list)?; api.register(networking_switch_port_list)?; api.register(networking_switch_port_status)?; @@ -4145,12 +4135,12 @@ async fn networking_switch_port_configuration_address_remove( /// List routes assigned to a provided interface configuration #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route", + path ="/v1/system/networking/switch-port-configuration/{configuration}/route", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_interface_route_list( +async fn networking_switch_port_configuration_route_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4171,12 +4161,12 @@ async fn networking_switch_port_configuration_interface_route_list( /// Add route to an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/add", + path ="/v1/system/networking/switch-port-configuration/{configuration}/route/add", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_interface_route_add( +async fn networking_switch_port_configuration_route_add( rqctx: RequestContext, - path_params: Path, + path_params: Path, route: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); @@ -4198,12 +4188,12 @@ async fn networking_switch_port_configuration_interface_route_add( /// Remove address from an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/remove", + path ="/v1/system/networking/switch-port-configuration/{configuration}/route/remove", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_interface_route_remove( +async fn networking_switch_port_configuration_route_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, route: TypedBody, ) -> Result { let apictx = rqctx.context(); @@ -4225,12 +4215,12 @@ async fn networking_switch_port_configuration_interface_route_remove( /// List bgp peers assigned to a provided interface configuration #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_interface_bgp_peer_list( +async fn networking_switch_port_configuration_bgp_peer_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4251,12 +4241,12 @@ async fn networking_switch_port_configuration_interface_bgp_peer_list( /// Add bgp peer to an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/add", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_interface_bgp_peer_add( +async fn networking_switch_port_configuration_bgp_peer_add( rqctx: RequestContext, - path_params: Path, + path_params: Path, bgp_peer: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); @@ -4278,12 +4268,12 @@ async fn networking_switch_port_configuration_interface_bgp_peer_add( /// Remove bgp peer from an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove", tags = ["system/networking"], }] -async fn networking_switch_port_configuration_interface_bgp_peer_remove( +async fn networking_switch_port_configuration_bgp_peer_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, bgp_peer: TypedBody, ) -> Result { let apictx = rqctx.context(); diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 435719b4654..de546a3028f 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -197,21 +197,21 @@ networking_loopback_address_list GET /v1/system/networking/loopback networking_switch_port_configuration_address_add POST /v1/system/networking/switch-port-configuration/{configuration}/address/add networking_switch_port_configuration_address_list GET /v1/system/networking/switch-port-configuration/{configuration}/address networking_switch_port_configuration_address_remove POST /v1/system/networking/switch-port-configuration/{configuration}/address/remove +networking_switch_port_configuration_bgp_peer_add POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/add +networking_switch_port_configuration_bgp_peer_list GET /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer +networking_switch_port_configuration_bgp_peer_remove POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove networking_switch_port_configuration_create POST /v1/system/networking/switch-port-configuration networking_switch_port_configuration_delete DELETE /v1/system/networking/switch-port-configuration networking_switch_port_configuration_geometry_set POST /v1/system/networking/switch-port-configuration/{configuration}/geometry networking_switch_port_configuration_geometry_view GET /v1/system/networking/switch-port-configuration/{configuration}/geometry -networking_switch_port_configuration_interface_bgp_peer_add POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add -networking_switch_port_configuration_interface_bgp_peer_list GET /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer -networking_switch_port_configuration_interface_bgp_peer_remove POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove -networking_switch_port_configuration_interface_route_add POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/add -networking_switch_port_configuration_interface_route_list GET /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route -networking_switch_port_configuration_interface_route_remove POST /v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/remove networking_switch_port_configuration_link_create POST /v1/system/networking/switch-port-configuration/{configuration}/link networking_switch_port_configuration_link_delete DELETE /v1/system/networking/switch-port-configuration/{configuration}/link/{link} networking_switch_port_configuration_link_list GET /v1/system/networking/switch-port-configuration/{configuration}/link networking_switch_port_configuration_link_view GET /v1/system/networking/switch-port-configuration/{configuration}/link/{link} networking_switch_port_configuration_list GET /v1/system/networking/switch-port-configuration +networking_switch_port_configuration_route_add POST /v1/system/networking/switch-port-configuration/{configuration}/route/add +networking_switch_port_configuration_route_list GET /v1/system/networking/switch-port-configuration/{configuration}/route +networking_switch_port_configuration_route_remove POST /v1/system/networking/switch-port-configuration/{configuration}/route/remove networking_switch_port_configuration_view GET /v1/system/networking/switch-port-configuration/{configuration} API operations found with tag "system/silos" diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index fa488d66857..d42d0610734 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -6,11 +6,11 @@ probe_view (get "/experimental/v1/probes/{probe ping (get "/v1/ping") networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status") networking_switch_port_configuration_address_list (get "/v1/system/networking/switch-port-configuration/{configuration}/address") +networking_switch_port_configuration_bgp_peer_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer") networking_switch_port_configuration_geometry_view (get "/v1/system/networking/switch-port-configuration/{configuration}/geometry") -networking_switch_port_configuration_interface_bgp_peer_list (get "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer") -networking_switch_port_configuration_interface_route_list (get "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route") networking_switch_port_configuration_link_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link") networking_switch_port_configuration_link_view (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}") +networking_switch_port_configuration_route_list (get "/v1/system/networking/switch-port-configuration/{configuration}/route") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") device_access_token (post "/device/token") @@ -20,9 +20,9 @@ login_local (post "/v1/login/{silo_name}/local") logout (post "/v1/logout") networking_switch_port_configuration_address_add (post "/v1/system/networking/switch-port-configuration/{configuration}/address/add") networking_switch_port_configuration_address_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/address/remove") +networking_switch_port_configuration_bgp_peer_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/add") +networking_switch_port_configuration_bgp_peer_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove") networking_switch_port_configuration_geometry_set (post "/v1/system/networking/switch-port-configuration/{configuration}/geometry") -networking_switch_port_configuration_interface_bgp_peer_add (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add") -networking_switch_port_configuration_interface_bgp_peer_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove") -networking_switch_port_configuration_interface_route_add (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/add") -networking_switch_port_configuration_interface_route_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/remove") networking_switch_port_configuration_link_create (post "/v1/system/networking/switch-port-configuration/{configuration}/link") +networking_switch_port_configuration_route_add (post "/v1/system/networking/switch-port-configuration/{configuration}/route/add") +networking_switch_port_configuration_route_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/route/remove") diff --git a/openapi/nexus.json b/openapi/nexus.json index 821c9277ad7..8429508862e 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7399,13 +7399,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/geometry": { + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer": { "get": { "tags": [ "system/networking" ], - "summary": "Get switch port geometry for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_geometry_view", + "summary": "List bgp peers assigned to a provided interface configuration", + "operationId": "networking_switch_port_configuration_bgp_peer_list", "parameters": [ { "in": "path", @@ -7423,7 +7423,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortConfig" + "$ref": "#/components/schemas/BgpPeerConfig" } } } @@ -7435,13 +7435,15 @@ "$ref": "#/components/responses/Error" } } - }, + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/add": { "post": { "tags": [ "system/networking" ], - "summary": "Set switch port geometry for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_geometry_set", + "summary": "Add bgp peer to an interface configuration", + "operationId": "networking_switch_port_configuration_bgp_peer_add", "parameters": [ { "in": "path", @@ -7457,7 +7459,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortConfigCreate" + "$ref": "#/components/schemas/BgpPeer" } } }, @@ -7469,7 +7471,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortConfig" + "$ref": "#/components/schemas/BgpPeer" } } } @@ -7483,13 +7485,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer": { - "get": { + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove": { + "post": { "tags": [ "system/networking" ], - "summary": "List bgp peers assigned to a provided interface configuration", - "operationId": "networking_switch_port_configuration_interface_bgp_peer_list", + "summary": "Remove bgp peer from an interface configuration", + "operationId": "networking_switch_port_configuration_bgp_peer_remove", "parameters": [ { "in": "path", @@ -7499,14 +7501,46 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpPeer" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "successful deletion" }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/geometry": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "Get switch port geometry for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_geometry_view", + "parameters": [ { "in": "path", - "name": "interface", - "description": "Interface name", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", "required": true, "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/NameOrId" } } ], @@ -7516,7 +7550,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BgpPeerConfig" + "$ref": "#/components/schemas/SwitchPortConfig" } } } @@ -7528,15 +7562,13 @@ "$ref": "#/components/responses/Error" } } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/add": { + }, "post": { "tags": [ "system/networking" ], - "summary": "Add bgp peer to an interface configuration", - "operationId": "networking_switch_port_configuration_interface_bgp_peer_add", + "summary": "Set switch port geometry for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_geometry_set", "parameters": [ { "in": "path", @@ -7546,22 +7578,13 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BgpPeer" + "$ref": "#/components/schemas/SwitchPortConfigCreate" } } }, @@ -7573,7 +7596,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BgpPeer" + "$ref": "#/components/schemas/SwitchPortConfig" } } } @@ -7587,13 +7610,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/bgp-peer/remove": { - "post": { + "/v1/system/networking/switch-port-configuration/{configuration}/link": { + "get": { "tags": [ "system/networking" ], - "summary": "Remove bgp peer from an interface configuration", - "operationId": "networking_switch_port_configuration_interface_bgp_peer_remove", + "summary": "List links for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_list", "parameters": [ { "in": "path", @@ -7603,31 +7626,23 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BgpPeer" + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_SwitchPortLinkConfig", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortLinkConfig" + } + } } } }, - "required": true - }, - "responses": { - "204": { - "description": "successful deletion" - }, "4XX": { "$ref": "#/components/responses/Error" }, @@ -7635,15 +7650,13 @@ "$ref": "#/components/responses/Error" } } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route": { - "get": { + }, + "post": { "tags": [ "system/networking" ], - "summary": "List routes assigned to a provided interface configuration", - "operationId": "networking_switch_port_configuration_interface_route_list", + "summary": "Create a link for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_create", "parameters": [ { "in": "path", @@ -7653,24 +7666,25 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "path", - "name": "interface", - "description": "Interface name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NamedLinkConfigCreate" + } + } + }, + "required": true + }, "responses": { - "200": { - "description": "successful operation", + "201": { + "description": "successful creation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RouteConfig" + "$ref": "#/components/schemas/SwitchPortLinkConfig" } } } @@ -7684,13 +7698,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/add": { - "post": { + "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}": { + "get": { "tags": [ "system/networking" ], - "summary": "Add route to an interface configuration", - "operationId": "networking_switch_port_configuration_interface_route_add", + "summary": "View a link for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_view", "parameters": [ { "in": "path", @@ -7703,31 +7717,21 @@ }, { "in": "path", - "name": "interface", - "description": "Interface name", + "name": "link", + "description": "Link name", "required": true, "schema": { "$ref": "#/components/schemas/Name" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Route" - } - } - }, - "required": true - }, "responses": { - "201": { - "description": "successful creation", + "200": { + "description": "successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Route" + "$ref": "#/components/schemas/SwitchPortLinkConfig" } } } @@ -7739,15 +7743,13 @@ "$ref": "#/components/responses/Error" } } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}/interface/{interface}/route/remove": { - "post": { + }, + "delete": { "tags": [ "system/networking" ], - "summary": "Remove address from an interface configuration", - "operationId": "networking_switch_port_configuration_interface_route_remove", + "summary": "Delete a link for a provided switch port configuration", + "operationId": "networking_switch_port_configuration_link_delete", "parameters": [ { "in": "path", @@ -7760,24 +7762,14 @@ }, { "in": "path", - "name": "interface", - "description": "Interface name", + "name": "link", + "description": "Link name", "required": true, "schema": { "$ref": "#/components/schemas/Name" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Route" - } - } - }, - "required": true - }, "responses": { "204": { "description": "successful deletion" @@ -7791,13 +7783,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/link": { + "/v1/system/networking/switch-port-configuration/{configuration}/route": { "get": { "tags": [ "system/networking" ], - "summary": "List links for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_link_list", + "summary": "List routes assigned to a provided interface configuration", + "operationId": "networking_switch_port_configuration_route_list", "parameters": [ { "in": "path", @@ -7815,11 +7807,7 @@ "content": { "application/json": { "schema": { - "title": "Array_of_SwitchPortLinkConfig", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortLinkConfig" - } + "$ref": "#/components/schemas/RouteConfig" } } } @@ -7831,13 +7819,15 @@ "$ref": "#/components/responses/Error" } } - }, + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/route/add": { "post": { "tags": [ "system/networking" ], - "summary": "Create a link for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_link_create", + "summary": "Add route to an interface configuration", + "operationId": "networking_switch_port_configuration_route_add", "parameters": [ { "in": "path", @@ -7853,7 +7843,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NamedLinkConfigCreate" + "$ref": "#/components/schemas/Route" } } }, @@ -7865,7 +7855,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortLinkConfig" + "$ref": "#/components/schemas/Route" } } } @@ -7879,13 +7869,13 @@ } } }, - "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}": { - "get": { + "/v1/system/networking/switch-port-configuration/{configuration}/route/remove": { + "post": { "tags": [ "system/networking" ], - "summary": "View a link for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_link_view", + "summary": "Remove address from an interface configuration", + "operationId": "networking_switch_port_configuration_route_remove", "parameters": [ { "in": "path", @@ -7895,62 +7885,18 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } } ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchPortLinkConfig" - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Route" } } }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, - "delete": { - "tags": [ - "system/networking" - ], - "summary": "Delete a link for a provided switch port configuration", - "operationId": "networking_switch_port_configuration_link_delete", - "parameters": [ - { - "in": "path", - "name": "configuration", - "description": "A name or id to use when selecting a switch port configuration.", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, - { - "in": "path", - "name": "link", - "description": "Link name", - "required": true, - "schema": { - "$ref": "#/components/schemas/Name" - } - } - ], + "required": true + }, "responses": { "204": { "description": "successful deletion" From 8416cac30d8a711e4c4f34ef312764db7d36bb16 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 30 Aug 2024 04:05:12 +0000 Subject: [PATCH 20/37] WIP: route add / remove --- nexus/db-model/src/schema.rs | 1 + .../src/db/datastore/switch_port.rs | 126 ++++++++++++++++++ nexus/src/app/switch_port.rs | 39 ++++++ nexus/src/external_api/http_entrypoints.rs | 33 +++-- nexus/types/src/external_api/params.rs | 21 +++ 5 files changed, 210 insertions(+), 10 deletions(-) diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index fd996210373..672660a9bc6 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -1910,4 +1910,5 @@ allow_tables_to_appear_in_same_query!( switch_port_settings_port_config, switch_port_settings_link_config, switch_port_settings_address_config, + switch_port_settings_route_config, ); diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index fd7b231ba52..ec0415bdda6 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -1195,6 +1195,132 @@ impl DataStore { }) } + pub async fn switch_port_configuration_route_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + ) -> ListResultVec { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings_route_config as route_config; + + let dataset = port_settings::table.inner_join( + route_config::table + .on(route_config::port_settings_id.eq(port_settings::id)), + ); + + let query = match configuration { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + let configs: Vec = query + .select(SwitchPortRouteConfig::as_select()) + .load_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e: diesel::result::Error| { + let msg = + "error while looking up interface route configuration"; + match e { + diesel::result::Error::NotFound => { + Error::non_resourcetype_not_found( + "could not find route configuration for switch port configuration: {configuration}, interface: {interface}" + )}, + _ => Error::internal_error(msg), + } + })?; + + Ok(configs) + } + + pub async fn switch_port_configuration_route_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + route: params::RouteAddRemove, + ) -> CreateResult { + use db::schema::address_lot; + use db::schema::switch_port_settings_route_config as route_config; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_interface_route_add", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let new_settings = route.clone(); + let err = err.clone(); + + async move { todo!() } + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_interface_route_add failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while adding route to interface", + ) + } + } + }) + } + + pub async fn switch_port_configuration_route_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + route: params::RouteAddRemove, + ) -> DeleteResult { + use db::schema::switch_port_settings_route_config as route_config; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_interface_route_remove", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let settings_to_remove = route.clone(); + let err = err.clone(); + + async move { todo!() } + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_interface_route_remove failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while removing route from interface", + ) + } + } + }) + } + // switch ports pub async fn switch_port_create( diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 38540c6e3cf..3d5a1f81908 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -252,6 +252,45 @@ impl super::Nexus { .await } + pub(crate) async fn switch_port_configuration_route_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + ) -> ListResultVec { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_route_list(opctx, configuration) + .await + } + + pub(crate) async fn switch_port_configuration_route_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + address: params::RouteAddRemove, + ) -> CreateResult { + opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_route_add(opctx, configuration, address) + .await + } + + pub(crate) async fn switch_port_configuration_route_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + address: params::RouteAddRemove, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_route_remove( + opctx, + configuration, + address, + ) + .await + } + async fn switch_port_create( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 4f495a3dad2..e75f6cb1b95 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -4145,11 +4145,14 @@ async fn networking_switch_port_configuration_route_list( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let configuration = path_params.into_inner().configuration; + let address = address.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("list interface routes") + let settings = nexus + .switch_port_configuration_route_list(&opctx, configuration) + .await?; + Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) }; apictx .context @@ -4168,15 +4171,18 @@ async fn networking_switch_port_configuration_route_add( rqctx: RequestContext, path_params: Path, route: TypedBody, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let configuration = path_params.into_inner().configuration; + let address = address.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("add interface route") + let settings = nexus + .switch_port_configuration_route_add(&opctx, configuration, address) + .await?; + Ok(HttpResponseCreated(settings.into())) }; apictx .context @@ -4199,11 +4205,18 @@ async fn networking_switch_port_configuration_route_remove( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let configuration = path_params.into_inner().configuration; + let address = address.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("remove interface route") + nexus + .switch_port_configuration_route_remove( + &opctx, + configuration, + address, + ) + .await?; + Ok(HttpResponseDeleted()) }; apictx .context diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index a4a66dc790e..cb77c1648d7 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1620,6 +1620,27 @@ pub struct Route { pub local_pref: Option, } +/// A route to a destination network through a gateway address to add or +/// remove to an interface. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RouteAddRemove { + /// The interface to configure the route on + pub interface: Name, + + /// The route destination. + pub dst: IpNet, + + /// The route gateway. + pub gw: IpAddr, + + /// VLAN id the gateway is reachable over. + pub vid: Option, + + /// Local preference for route. Higher preference indictes precedence + /// within and across protocols. + pub local_pref: Option, +} + /// Select a BGP config by a name or id. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct BgpConfigSelector { From d991823fc4280ca9234afd1012ec84379ded1056 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 30 Aug 2024 06:03:25 +0000 Subject: [PATCH 21/37] WIP: route add / remove --- nexus/src/app/switch_port.rs | 13 ++--- nexus/src/external_api/http_entrypoints.rs | 21 ++++---- openapi/nexus.json | 58 ++++++++++++++++++++-- 3 files changed, 69 insertions(+), 23 deletions(-) diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 3d5a1f81908..eeae69f6a4e 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -12,6 +12,7 @@ use nexus_db_model::SwitchPortAddressConfig; use nexus_db_model::SwitchPortConfig; use nexus_db_model::SwitchPortGeometry; use nexus_db_model::SwitchPortLinkConfig; +use nexus_db_model::SwitchPortRouteConfig; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; @@ -267,11 +268,11 @@ impl super::Nexus { &self, opctx: &OpContext, configuration: NameOrId, - address: params::RouteAddRemove, + route: params::RouteAddRemove, ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; self.db_datastore - .switch_port_configuration_route_add(opctx, configuration, address) + .switch_port_configuration_route_add(opctx, configuration, route) .await } @@ -279,15 +280,11 @@ impl super::Nexus { &self, opctx: &OpContext, configuration: NameOrId, - address: params::RouteAddRemove, + route: params::RouteAddRemove, ) -> DeleteResult { opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; self.db_datastore - .switch_port_configuration_route_remove( - opctx, - configuration, - address, - ) + .switch_port_configuration_route_remove(opctx, configuration, route) .await } diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index e75f6cb1b95..69a09f2d9be 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -41,11 +41,10 @@ use nexus_db_queries::db::lookup::ImageLookup; use nexus_db_queries::db::lookup::ImageParentLookup; use nexus_db_queries::db::model::Name; use nexus_types::external_api::{ - params::{BgpPeerConfig, RouteConfig}, + params::BgpPeerConfig, shared::{BfdStatus, ProbeInfo}, }; use omicron_common::api::external::AddressLot; -use omicron_common::api::external::AddressLotBlock; use omicron_common::api::external::AddressLotCreateResponse; use omicron_common::api::external::AggregateBgpMessageHistory; use omicron_common::api::external::BgpAnnounceSet; @@ -83,6 +82,7 @@ use omicron_common::api::external::{ }, SwitchPortAddressConfig, }; +use omicron_common::api::external::{AddressLotBlock, SwitchPortRouteConfig}; use omicron_common::bail_unless; use omicron_uuid_kinds::GenericUuid; use parse_display::Display; @@ -4141,12 +4141,11 @@ async fn networking_switch_port_configuration_address_remove( async fn networking_switch_port_configuration_route_list( rqctx: RequestContext, path_params: Path, -) -> Result, HttpError> { +) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; let configuration = path_params.into_inner().configuration; - let address = address.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus @@ -4170,17 +4169,17 @@ async fn networking_switch_port_configuration_route_list( async fn networking_switch_port_configuration_route_add( rqctx: RequestContext, path_params: Path, - route: TypedBody, -) -> Result, HttpError> { + route: TypedBody, +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; let configuration = path_params.into_inner().configuration; - let address = address.into_inner(); + let route = route.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus - .switch_port_configuration_route_add(&opctx, configuration, address) + .switch_port_configuration_route_add(&opctx, configuration, route) .await?; Ok(HttpResponseCreated(settings.into())) }; @@ -4200,20 +4199,20 @@ async fn networking_switch_port_configuration_route_add( async fn networking_switch_port_configuration_route_remove( rqctx: RequestContext, path_params: Path, - route: TypedBody, + route: TypedBody, ) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; let configuration = path_params.into_inner().configuration; - let address = address.into_inner(); + let route = route.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; nexus .switch_port_configuration_route_remove( &opctx, configuration, - address, + route, ) .await?; Ok(HttpResponseDeleted()) diff --git a/openapi/nexus.json b/openapi/nexus.json index 8429508862e..bc6fe94ab90 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7807,7 +7807,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RouteConfig" + "title": "Array_of_SwitchPortRouteConfig", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortRouteConfig" + } } } } @@ -7843,7 +7847,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Route" + "$ref": "#/components/schemas/RouteAddRemove" } } }, @@ -7855,7 +7859,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Route" + "$ref": "#/components/schemas/SwitchPortRouteConfig" } } } @@ -7891,7 +7895,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Route" + "$ref": "#/components/schemas/RouteAddRemove" } } }, @@ -18182,6 +18186,52 @@ "gw" ] }, + "RouteAddRemove": { + "description": "A route to a destination network through a gateway address to add or remove to an interface.", + "type": "object", + "properties": { + "dst": { + "description": "The route destination.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "gw": { + "description": "The route gateway.", + "type": "string", + "format": "ip" + }, + "interface": { + "description": "The interface to configure the route on", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "local_pref": { + "nullable": true, + "description": "Local preference for route. Higher preference indictes precedence within and across protocols.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "vid": { + "nullable": true, + "description": "VLAN id the gateway is reachable over.", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "dst", + "gw", + "interface" + ] + }, "RouteConfig": { "description": "Route configuration data associated with a switch port configuration.", "type": "object", From de92b30f21d1f7a623ecd3a290573780408ae6cb Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 30 Aug 2024 07:14:00 +0000 Subject: [PATCH 22/37] WIP: route add / remove / list --- .../src/db/datastore/switch_port.rs | 96 ++++++++++++++++++- 1 file changed, 91 insertions(+), 5 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index ec0415bdda6..3b58c56331e 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -981,7 +981,7 @@ impl DataStore { }, _ => { err.bail(Error::internal_error( - "error while looking up address lot for interface address" + "error while looking up configuration for interface address" )) }, } @@ -1133,7 +1133,7 @@ impl DataStore { }, _ => { err.bail(Error::internal_error( - "error while looking up address lot for interface address" + "error while looking up configuration for interface address" )) }, } @@ -1246,7 +1246,6 @@ impl DataStore { configuration: NameOrId, route: params::RouteAddRemove, ) -> CreateResult { - use db::schema::address_lot; use db::schema::switch_port_settings_route_config as route_config; let conn = self.pool_connection_authorized(opctx).await?; @@ -1260,7 +1259,61 @@ impl DataStore { let new_settings = route.clone(); let err = err.clone(); - async move { todo!() } + async move { + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up configuration for interface address" + )) + }, + } + })?; + + let route_config = SwitchPortRouteConfig{ + port_settings_id, + interface_name: new_settings.interface.to_string(), + dst: new_settings.dst.into(), + gw: new_settings.gw.into(), + vid: new_settings.vid.map(Into::into), + local_pref: new_settings.local_pref.map(Into::into), + }; + + let config = diesel::insert_into(route_config::table) + .values(route_config) + .returning(SwitchPortRouteConfig::as_returning()) + .get_result_async(&conn) + .await + .map_err(|e: diesel::result::Error| { + let message = "error while adding route to interface"; + match e { + diesel::result::Error::DatabaseError(kind, _) => { + match kind { + diesel::result::DatabaseErrorKind::UniqueViolation => { + err.bail(Error::conflict("route configuration conflicts with an existing route")) + }, + diesel::result::DatabaseErrorKind::NotNullViolation => { + err.bail(Error::invalid_request("a required field is not populated")) + }, + _ => err.bail(Error::internal_error(message)), + } + }, + _ => err.bail(Error::internal_error(message)), + } + })?; + + Ok(config) + } }) .await .map_err(|e| { @@ -1300,7 +1353,40 @@ impl DataStore { let settings_to_remove = route.clone(); let err = err.clone(); - async move { todo!() } + async move { + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up configuration for interface address" + )) + }, + } + })?; + + // delete route config + // PRIMARY KEY (port_settings_id, interface_name, dst, gw) + diesel::delete(route_config::table) + .filter(route_config::dst.eq(IpNetwork::from(settings_to_remove.dst))) + .filter(route_config::gw.eq(IpNetwork::from(settings_to_remove.gw))) + .filter(route_config::port_settings_id.eq(port_settings_id)) + .filter(route_config::interface_name.eq(settings_to_remove.interface.to_string())) + .execute_async(&conn) + .await?; + + Ok(()) + + } }) .await .map_err(|e| { From 22fe92a47729c4a25bf9b96acb7bcdc08afd498b Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 30 Aug 2024 19:04:40 +0000 Subject: [PATCH 23/37] add bgp peer list / add / remove --- nexus/db-model/src/schema.rs | 4 + nexus/db-queries/src/db/datastore/mod.rs | 1 + .../src/db/datastore/switch_port.rs | 1185 ++++++++++++----- nexus/src/app/switch_port.rs | 45 + nexus/src/external_api/http_entrypoints.rs | 76 +- nexus/types/src/external_api/params.rs | 7 + openapi/nexus.json | 8 +- 7 files changed, 992 insertions(+), 334 deletions(-) diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index 672660a9bc6..c50609399c5 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -1911,4 +1911,8 @@ allow_tables_to_appear_in_same_query!( switch_port_settings_link_config, switch_port_settings_address_config, switch_port_settings_route_config, + switch_port_settings_bgp_peer_config, + switch_port_settings_bgp_peer_config_allow_export, + switch_port_settings_bgp_peer_config_allow_import, + switch_port_settings_bgp_peer_config_communities, ); diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index 26ee36271d4..a163d2b0e1c 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -120,6 +120,7 @@ pub use region::RegionAllocationParameters; pub use silo::Discoverability; pub use sled::SledTransition; pub use sled::TransitionError; +pub use switch_port::BgpPeerConfig; pub use switch_port::SwitchPortSettingsCombinedResult; pub use virtual_provisioning_collection::StorageType; pub use vmm::VmmStateUpdateResult; diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 3b58c56331e..31948df6e4a 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -39,7 +39,7 @@ use nexus_db_model::{ use nexus_types::external_api::params; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ - self, CreateResult, DataPageParams, DeleteResult, Error, + self, BgpPeer, CreateResult, DataPageParams, DeleteResult, Error, ImportExportPolicy, ListResultVec, LookupResult, NameOrId, ResourceType, UpdateResult, }; @@ -265,29 +265,29 @@ impl DataStore { async move { do_switch_port_settings_delete(&conn, &selector, err).await } - }) - .await - .map_err(|e| { - if let Some(err) = err.take() { - match err { - SwitchPortSettingsDeleteError::SwitchPortSettingsNotFound => { - Error::invalid_request("port settings not found") + }) + .await + .map_err(|e| { + if let Some(err) = err.take() { + match err { + SwitchPortSettingsDeleteError::SwitchPortSettingsNotFound => { + Error::invalid_request("port settings not found") + } } + } else { + let name = match ¶ms.configuration { + Some(name_or_id) => name_or_id.to_string(), + None => String::new(), + }; + public_error_from_diesel( + e, + ErrorHandler::Conflict( + ResourceType::SwitchPortSettings, + &name, + ), + ) } - } else { - let name = match ¶ms.configuration { - Some(name_or_id) => name_or_id.to_string(), - None => String::new(), - }; - public_error_from_diesel( - e, - ErrorHandler::Conflict( - ResourceType::SwitchPortSettings, - &name, - ), - ) - } - }) + }) } pub async fn switch_port_settings_update( @@ -387,39 +387,39 @@ impl DataStore { .transaction(&conn, |conn| { let err = err.clone(); async move { - // get the top level port settings object - use db::schema::switch_port_settings::{ - self, dsl as port_settings_dsl, - }; - use db::schema::{ - switch_port_settings_bgp_peer_config_allow_import::dsl as allow_import_dsl, - switch_port_settings_bgp_peer_config_allow_export::dsl as allow_export_dsl, - switch_port_settings_bgp_peer_config_communities::dsl as bgp_communities_dsl, - }; - - let id = match name_or_id { - NameOrId::Id(id) => *id, - NameOrId::Name(name) => { - let name_str = name.to_string(); - port_settings_dsl::switch_port_settings - .filter(switch_port_settings::time_deleted.is_null()) - .filter(switch_port_settings::name.eq(name_str)) - .select(switch_port_settings::id) - .limit(1) - .first_async::(&conn) - .await - .map_err(|diesel_error| { - err.bail_retryable_or_else(diesel_error, |_| { - SwitchPortSettingsGetError::NotFound( - name.clone(), - ) - }) - })? - } - }; + // get the top level port settings object + use db::schema::switch_port_settings::{ + self, dsl as port_settings_dsl, + }; + use db::schema::{ + switch_port_settings_bgp_peer_config_allow_import::dsl as allow_import_dsl, + switch_port_settings_bgp_peer_config_allow_export::dsl as allow_export_dsl, + switch_port_settings_bgp_peer_config_communities::dsl as bgp_communities_dsl, + }; - let settings: SwitchPortSettings = - port_settings_dsl::switch_port_settings + let id = match name_or_id { + NameOrId::Id(id) => *id, + NameOrId::Name(name) => { + let name_str = name.to_string(); + port_settings_dsl::switch_port_settings + .filter(switch_port_settings::time_deleted.is_null()) + .filter(switch_port_settings::name.eq(name_str)) + .select(switch_port_settings::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|diesel_error| { + err.bail_retryable_or_else(diesel_error, |_| { + SwitchPortSettingsGetError::NotFound( + name.clone(), + ) + }) + })? + } + }; + + let settings: SwitchPortSettings = + port_settings_dsl::switch_port_settings .filter(switch_port_settings::time_deleted.is_null()) .filter(switch_port_settings::id.eq(id)) .select(SwitchPortSettings::as_select()) @@ -427,100 +427,100 @@ impl DataStore { .first_async::(&conn) .await?; - // get the port config - use db::schema::switch_port_settings_port_config::{ - self as port_config, dsl as port_config_dsl, - }; - let port: SwitchPortConfig = - port_config_dsl::switch_port_settings_port_config + // get the port config + use db::schema::switch_port_settings_port_config::{ + self as port_config, dsl as port_config_dsl, + }; + let port: SwitchPortConfig = + port_config_dsl::switch_port_settings_port_config .filter(port_config::port_settings_id.eq(id)) .select(SwitchPortConfig::as_select()) .limit(1) .first_async::(&conn) .await?; - // initialize result - let mut result = - SwitchPortSettingsCombinedResult::new(settings, port); + // initialize result + let mut result = + SwitchPortSettingsCombinedResult::new(settings, port); - // get the link configs - use db::schema::switch_port_settings_link_config::{ - self as link_config, dsl as link_config_dsl, - }; + // get the link configs + use db::schema::switch_port_settings_link_config::{ + self as link_config, dsl as link_config_dsl, + }; - result.links = link_config_dsl::switch_port_settings_link_config - .filter(link_config::port_settings_id.eq(id)) - .select(SwitchPortLinkConfig::as_select()) - .load_async::(&conn) - .await?; + result.links = link_config_dsl::switch_port_settings_link_config + .filter(link_config::port_settings_id.eq(id)) + .select(SwitchPortLinkConfig::as_select()) + .load_async::(&conn) + .await?; - let lldp_link_ids: Vec = result - .links - .iter() - .map(|link| link.lldp_link_config_id) - .collect(); + let lldp_link_ids: Vec = result + .links + .iter() + .map(|link| link.lldp_link_config_id) + .collect(); - use db::schema::lldp_link_config; - result.link_lldp = lldp_link_config::dsl::lldp_link_config - .filter(lldp_link_config::id.eq_any(lldp_link_ids)) - .select(LldpLinkConfig::as_select()) - .limit(1) - .load_async::(&conn) - .await?; + use db::schema::lldp_link_config; + result.link_lldp = lldp_link_config::dsl::lldp_link_config + .filter(lldp_link_config::id.eq_any(lldp_link_ids)) + .select(LldpLinkConfig::as_select()) + .limit(1) + .load_async::(&conn) + .await?; - // get the interface configs - use db::schema::switch_port_settings_interface_config::{ - self as interface_config, dsl as interface_config_dsl, - }; + // get the interface configs + use db::schema::switch_port_settings_interface_config::{ + self as interface_config, dsl as interface_config_dsl, + }; - result.interfaces = - interface_config_dsl::switch_port_settings_interface_config + result.interfaces = + interface_config_dsl::switch_port_settings_interface_config .filter(interface_config::port_settings_id.eq(id)) .select(SwitchInterfaceConfig::as_select()) .load_async::(&conn) .await?; - use db::schema::switch_vlan_interface_config as vlan_config; - use db::schema::switch_vlan_interface_config::dsl as vlan_dsl; - let interface_ids: Vec = result - .interfaces - .iter() - .map(|interface| interface.id) - .collect(); - - result.vlan_interfaces = vlan_dsl::switch_vlan_interface_config - .filter(vlan_config::interface_config_id.eq_any(interface_ids)) - .select(SwitchVlanInterfaceConfig::as_select()) - .load_async::(&conn) - .await?; + use db::schema::switch_vlan_interface_config as vlan_config; + use db::schema::switch_vlan_interface_config::dsl as vlan_dsl; + let interface_ids: Vec = result + .interfaces + .iter() + .map(|interface| interface.id) + .collect(); + + result.vlan_interfaces = vlan_dsl::switch_vlan_interface_config + .filter(vlan_config::interface_config_id.eq_any(interface_ids)) + .select(SwitchVlanInterfaceConfig::as_select()) + .load_async::(&conn) + .await?; - // get the route configs - use db::schema::switch_port_settings_route_config::{ - self as route_config, dsl as route_config_dsl, - }; + // get the route configs + use db::schema::switch_port_settings_route_config::{ + self as route_config, dsl as route_config_dsl, + }; - result.routes = route_config_dsl::switch_port_settings_route_config - .filter(route_config::port_settings_id.eq(id)) - .select(SwitchPortRouteConfig::as_select()) - .load_async::(&conn) - .await?; + result.routes = route_config_dsl::switch_port_settings_route_config + .filter(route_config::port_settings_id.eq(id)) + .select(SwitchPortRouteConfig::as_select()) + .load_async::(&conn) + .await?; - // get the bgp peer configs - use db::schema::switch_port_settings_bgp_peer_config::{ - self as bgp_peer, dsl as bgp_peer_dsl, - }; + // get the bgp peer configs + use db::schema::switch_port_settings_bgp_peer_config::{ + self as bgp_peer, dsl as bgp_peer_dsl, + }; - let peers: Vec = - bgp_peer_dsl::switch_port_settings_bgp_peer_config + let peers: Vec = + bgp_peer_dsl::switch_port_settings_bgp_peer_config .filter(bgp_peer::port_settings_id.eq(id)) .select(SwitchPortBgpPeerConfig::as_select()) .load_async::(&conn) .await?; - for p in peers.iter() { - let allowed_import: ImportExportPolicy = if p.allow_import_list_active { - let db_list: Vec = - allow_import_dsl::switch_port_settings_bgp_peer_config_allow_import + for p in peers.iter() { + let allowed_import: ImportExportPolicy = if p.allow_import_list_active { + let db_list: Vec = + allow_import_dsl::switch_port_settings_bgp_peer_config_allow_import .filter(allow_import_dsl::port_settings_id.eq(id)) .filter(allow_import_dsl::interface_name.eq(p.interface_name.clone())) .filter(allow_import_dsl::addr.eq(p.addr)) @@ -528,18 +528,18 @@ impl DataStore { .load_async::(&conn) .await?; - ImportExportPolicy::Allow(db_list - .into_iter() - .map(|x| x.prefix.into()) - .collect() - ) - } else { - ImportExportPolicy::NoFiltering - }; + ImportExportPolicy::Allow(db_list + .into_iter() + .map(|x| x.prefix.into()) + .collect() + ) + } else { + ImportExportPolicy::NoFiltering + }; - let allowed_export: ImportExportPolicy = if p.allow_export_list_active { - let db_list: Vec = - allow_export_dsl::switch_port_settings_bgp_peer_config_allow_export + let allowed_export: ImportExportPolicy = if p.allow_export_list_active { + let db_list: Vec = + allow_export_dsl::switch_port_settings_bgp_peer_config_allow_export .filter(allow_export_dsl::port_settings_id.eq(id)) .filter(allow_export_dsl::interface_name.eq(p.interface_name.clone())) .filter(allow_export_dsl::addr.eq(p.addr)) @@ -547,17 +547,17 @@ impl DataStore { .load_async::(&conn) .await?; - ImportExportPolicy::Allow(db_list - .into_iter() - .map(|x| x.prefix.into()) - .collect() - ) - } else { - ImportExportPolicy::NoFiltering - }; + ImportExportPolicy::Allow(db_list + .into_iter() + .map(|x| x.prefix.into()) + .collect() + ) + } else { + ImportExportPolicy::NoFiltering + }; - let communities: Vec = - bgp_communities_dsl::switch_port_settings_bgp_peer_config_communities + let communities: Vec = + bgp_communities_dsl::switch_port_settings_bgp_peer_config_communities .filter(bgp_communities_dsl::port_settings_id.eq(id)) .filter(bgp_communities_dsl::interface_name.eq(p.interface_name.clone())) .filter(bgp_communities_dsl::addr.eq(p.addr)) @@ -565,68 +565,68 @@ impl DataStore { .load_async::(&conn) .await?; - let view = BgpPeerConfig { - port_settings_id: p.port_settings_id, - bgp_config_id: p.bgp_config_id, - interface_name: p.interface_name.clone(), - addr: p.addr, - hold_time: p.hold_time, - idle_hold_time: p.idle_hold_time, - delay_open: p.delay_open, - connect_retry: p.connect_retry, - keepalive: p.keepalive, - remote_asn: p.remote_asn, - min_ttl: p.min_ttl, - md5_auth_key: p.md5_auth_key.clone(), - multi_exit_discriminator: p.multi_exit_discriminator, - local_pref: p.local_pref, - enforce_first_as: p.enforce_first_as, - vlan_id: p.vlan_id, - communities: communities.into_iter().map(|c| c.community.0).collect(), - allowed_import, - allowed_export, - }; - - result.bgp_peers.push(view); - } + let view = BgpPeerConfig { + port_settings_id: p.port_settings_id, + bgp_config_id: p.bgp_config_id, + interface_name: p.interface_name.clone(), + addr: p.addr, + hold_time: p.hold_time, + idle_hold_time: p.idle_hold_time, + delay_open: p.delay_open, + connect_retry: p.connect_retry, + keepalive: p.keepalive, + remote_asn: p.remote_asn, + min_ttl: p.min_ttl, + md5_auth_key: p.md5_auth_key.clone(), + multi_exit_discriminator: p.multi_exit_discriminator, + local_pref: p.local_pref, + enforce_first_as: p.enforce_first_as, + vlan_id: p.vlan_id, + communities: communities.into_iter().map(|c| c.community.0).collect(), + allowed_import, + allowed_export, + }; + + result.bgp_peers.push(view); + } - // get the address configs - use db::schema::switch_port_settings_address_config::{ - self as address_config, dsl as address_config_dsl, - }; + // get the address configs + use db::schema::switch_port_settings_address_config::{ + self as address_config, dsl as address_config_dsl, + }; - result.addresses = - address_config_dsl::switch_port_settings_address_config + result.addresses = + address_config_dsl::switch_port_settings_address_config .filter(address_config::port_settings_id.eq(id)) .select(SwitchPortAddressConfig::as_select()) .load_async::(&conn) .await?; - Ok(result) - } - }) - .await - .map_err(|e| { - if let Some(err) = err.take() { - match err { - SwitchPortSettingsGetError::NotFound(name) => { - Error::not_found_by_name( + Ok(result) + } + }) + .await + .map_err(|e| { + if let Some(err) = err.take() { + match err { + SwitchPortSettingsGetError::NotFound(name) => { + Error::not_found_by_name( + ResourceType::SwitchPortSettings, + &name, + ) + } + } + } else { + let name = name_or_id.to_string(); + public_error_from_diesel( + e, + ErrorHandler::Conflict( ResourceType::SwitchPortSettings, &name, - ) - } + ), + ) } - } else { - let name = name_or_id.to_string(); - public_error_from_diesel( - e, - ErrorHandler::Conflict( - ResourceType::SwitchPortSettings, - &name, - ), - ) - } - }) + }) } pub async fn switch_port_configuration_geometry_get( @@ -958,8 +958,8 @@ impl DataStore { let err = OptionalError::new(); self.transaction_retry_wrapper( - "switch_port_configuration_interface_address_add", - ) + "switch_port_configuration_interface_address_add", + ) .transaction(&conn, |conn| { let parent_configuration = configuration.clone(); let new_settings = address.clone(); @@ -1074,10 +1074,10 @@ impl DataStore { let address = diesel::insert_into( address_config::table, ) - .values(address_config) - .returning(SwitchPortAddressConfig::as_returning()) - .get_result_async(&conn) - .await?; + .values(address_config) + .returning(SwitchPortAddressConfig::as_returning()) + .get_result_async(&conn) + .await?; Ok(address) } @@ -1110,8 +1110,8 @@ impl DataStore { let err = OptionalError::new(); self.transaction_retry_wrapper( - "switch_port_configuration_interface_address_remove", - ) + "switch_port_configuration_interface_address_remove", + ) .transaction(&conn, |conn| { let parent_configuration = configuration.clone(); let settings_to_remove = address.clone(); @@ -1254,84 +1254,84 @@ impl DataStore { self.transaction_retry_wrapper( "switch_port_configuration_interface_route_add", ) - .transaction(&conn, |conn| { - let parent_configuration = configuration.clone(); - let new_settings = route.clone(); - let err = err.clone(); - - async move { - // resolve id of port_settings record - let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) - .await - .map_err(|e: diesel::result::Error| { - match e { - diesel::result::Error::NotFound => { - err.bail( - Error::non_resourcetype_not_found( - format!("unable to lookup configuration with identifier {parent_configuration}") + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let new_settings = route.clone(); + let err = err.clone(); + + async move { + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) ) - ) - }, - _ => { - err.bail(Error::internal_error( - "error while looking up configuration for interface address" - )) - }, - } - })?; - - let route_config = SwitchPortRouteConfig{ - port_settings_id, - interface_name: new_settings.interface.to_string(), - dst: new_settings.dst.into(), - gw: new_settings.gw.into(), - vid: new_settings.vid.map(Into::into), - local_pref: new_settings.local_pref.map(Into::into), - }; - - let config = diesel::insert_into(route_config::table) - .values(route_config) - .returning(SwitchPortRouteConfig::as_returning()) - .get_result_async(&conn) - .await - .map_err(|e: diesel::result::Error| { - let message = "error while adding route to interface"; - match e { - diesel::result::Error::DatabaseError(kind, _) => { - match kind { - diesel::result::DatabaseErrorKind::UniqueViolation => { - err.bail(Error::conflict("route configuration conflicts with an existing route")) - }, - diesel::result::DatabaseErrorKind::NotNullViolation => { - err.bail(Error::invalid_request("a required field is not populated")) - }, - _ => err.bail(Error::internal_error(message)), - } - }, - _ => err.bail(Error::internal_error(message)), - } - })?; + }, + _ => { + err.bail(Error::internal_error( + "error while looking up configuration for interface route" + )) + }, + } + })?; - Ok(config) - } - }) - .await - .map_err(|e| { - let message = - "switch_port_configuration_interface_route_add failed"; - match err.take() { - Some(external_error) => { - error!(opctx.log, "{message}"; "error" => ?external_error); - external_error + let route_config = SwitchPortRouteConfig{ + port_settings_id, + interface_name: new_settings.interface.to_string(), + dst: new_settings.dst.into(), + gw: new_settings.gw.into(), + vid: new_settings.vid.map(Into::into), + local_pref: new_settings.local_pref.map(Into::into), + }; + + let config = diesel::insert_into(route_config::table) + .values(route_config) + .returning(SwitchPortRouteConfig::as_returning()) + .get_result_async(&conn) + .await + .map_err(|e: diesel::result::Error| { + let message = "error while adding route to interface"; + match e { + diesel::result::Error::DatabaseError(kind, _) => { + match kind { + diesel::result::DatabaseErrorKind::UniqueViolation => { + err.bail(Error::conflict("route configuration conflicts with an existing route")) + }, + diesel::result::DatabaseErrorKind::NotNullViolation => { + err.bail(Error::invalid_request("a required field is not populated")) + }, + _ => err.bail(Error::internal_error(message)), + } + }, + _ => err.bail(Error::internal_error(message)), + } + })?; + + Ok(config) } - None => { - error!(opctx.log, "{message}"; "error" => ?e); - Error::internal_error( - "error while adding route to interface", - ) + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_interface_route_add failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while adding route to interface", + ) + } } - } - }) + }) } pub async fn switch_port_configuration_route_remove( @@ -1348,12 +1348,12 @@ impl DataStore { self.transaction_retry_wrapper( "switch_port_configuration_interface_route_remove", ) - .transaction(&conn, |conn| { - let parent_configuration = configuration.clone(); - let settings_to_remove = route.clone(); - let err = err.clone(); + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let settings_to_remove = route.clone(); + let err = err.clone(); - async move { + async move { // resolve id of port_settings record let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) .await @@ -1368,7 +1368,7 @@ impl DataStore { }, _ => { err.bail(Error::internal_error( - "error while looking up configuration for interface address" + "error while looking up configuration for interface route" )) }, } @@ -1386,25 +1386,610 @@ impl DataStore { Ok(()) - } - }) - .await - .map_err(|e| { - let message = - "switch_port_configuration_interface_route_remove failed"; - match err.take() { - Some(external_error) => { - error!(opctx.log, "{message}"; "error" => ?external_error); - external_error } - None => { - error!(opctx.log, "{message}"; "error" => ?e); - Error::internal_error( - "error while removing route from interface", - ) + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_interface_route_remove failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while removing route from interface", + ) + } } - } - }) + }) + } + + pub async fn switch_port_configuration_bgp_peer_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + ) -> ListResultVec { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings_bgp_peer_config as bgp_peer_config; + use db::schema::switch_port_settings_bgp_peer_config_allow_export as allow_export; + use db::schema::switch_port_settings_bgp_peer_config_allow_import as allow_import; + use db::schema::switch_port_settings_bgp_peer_config_communities as communities; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_bgp_peer_list", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let err = err.clone(); + + async move { + + let dataset = port_settings::table.inner_join( + bgp_peer_config::table + .on(bgp_peer_config::port_settings_id.eq(port_settings::id)), + ); + + let query = match parent_configuration { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + let peers: Vec = query + .select(SwitchPortBgpPeerConfig::as_select()) + .load_async(&conn) + .await + .map_err(|e: diesel::result::Error| { + let message = "error while looking up interface bgp peer configuration"; + error!(opctx.log, "{message}"; "error" => ?e); + match e { + diesel::result::Error::NotFound => { + err.bail(Error::non_resourcetype_not_found( + "could not find bgp peer configuration for switch port configuration: {configuration}, interface: {interface}" + ))}, + _ => err.bail(Error::internal_error(message)), + } + })?; + + let mut configs: Vec = vec![]; + + for peer in peers { + // get allowed import + let prefixes_to_import: Vec = allow_import::table + .filter(allow_import::addr.eq(peer.addr)) + .filter(allow_import::interface_name.eq(peer.interface_name.clone())) + .filter(allow_import::port_settings_id.eq(peer.port_settings_id)) + .select(allow_import::prefix) + .load_async(&conn) + .await + .map_err(|e| { + let message = "error while looking up bgp peer import configuration"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + + let allowed_import = if prefixes_to_import.is_empty() { + ImportExportPolicy::NoFiltering + } else { + ImportExportPolicy::Allow(prefixes_to_import.into_iter().map(Into::into).collect()) + }; + + // get allowed export + let prefixes_to_export: Vec = allow_export::table + .filter(allow_export::addr.eq(peer.addr)) + .filter(allow_export::interface_name.eq(peer.interface_name.clone())) + .filter(allow_export::port_settings_id.eq(peer.port_settings_id)) + .select(allow_export::prefix) + .load_async(&conn) + .await + .map_err(|e| { + let message = + "error while looking up bgp peer export configuration"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + + let allowed_export = if prefixes_to_export.is_empty() { + ImportExportPolicy::NoFiltering + } else { + ImportExportPolicy::Allow(prefixes_to_export.into_iter().map(Into::into).collect()) + }; + + // get communities + let communities: Vec = communities::table + .filter(communities::addr.eq(peer.addr)) + .filter(communities::interface_name.eq(peer.interface_name.clone())) + .filter(communities::port_settings_id.eq(peer.port_settings_id)) + .select(communities::community) + .load_async(&conn) + .await + .map_err(|e| { + let message = + "error while looking up bgp peer communities"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + + // build config + let config = BgpPeerConfig { + port_settings_id: peer.port_settings_id, + bgp_config_id: peer.bgp_config_id, + interface_name: peer.interface_name, + addr: peer.addr, + hold_time: peer.hold_time, + idle_hold_time: peer.idle_hold_time, + delay_open: peer.delay_open, + connect_retry: peer.connect_retry, + keepalive: peer.keepalive, + remote_asn: peer.remote_asn, + min_ttl: peer.min_ttl, + md5_auth_key: peer.md5_auth_key, + multi_exit_discriminator: peer.multi_exit_discriminator, + local_pref: peer.local_pref, + enforce_first_as: peer.enforce_first_as, + allowed_import, + allowed_export, + communities: communities.into_iter().map(Into::into).collect(), + vlan_id: peer.vlan_id, + }; + // push + configs.push(config); + } + + Ok(configs) + } + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_bgp_peer_list failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while listing peers for interface configuration", + ) + } + } + }) + } + + pub async fn switch_port_configuration_bgp_peer_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: BgpPeer, + ) -> CreateResult { + use db::schema::bgp_config; + use db::schema::switch_port_settings_bgp_peer_config as bgp_peer_config; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_interface_bgp_peer_add", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let new_settings = bgp_peer.clone(); + let err = err.clone(); + + async move { + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up configuration for interface bgp peer" + )) + }, + } + })?; + + // resolve id of referenced bgp configuratino + let bgp_config_id = match new_settings.bgp_config { + + NameOrId::Id(id) => { + // verify id is valid + bgp_config::table + .filter(bgp_config::time_deleted.is_null()) + .filter(bgp_config::id.eq(id)) + .select(bgp_config::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail(Error::not_found_by_id(ResourceType::BgpConfig, &id)) + }, + _ => { + let message = "error while looking up bgp config for bgp peer"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + }, + } + }) + }, + + NameOrId::Name(name) => { + bgp_config::table + .filter(bgp_config::time_deleted.is_null()) + .filter(bgp_config::name.eq(name.to_string())) + .select(bgp_config::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail(Error::not_found_by_name(ResourceType::BgpConfig, &name)) + }, + _ => { + let message = "error while looking up bgp config for bgp peer"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + }, + } + }) + } + }?; + + // create import list if enabled + let allow_import_list_active = match new_settings.allowed_import.clone() { + ImportExportPolicy::NoFiltering => false, + ImportExportPolicy::Allow(prefixes) => { + use db::schema::switch_port_settings_bgp_peer_config_allow_import as allow_import; + let to_insert: Vec = prefixes + .clone() + .into_iter() + .map(|x| SwitchPortBgpPeerConfigAllowImport { + port_settings_id, + interface_name: new_settings.interface_name.clone(), + addr: new_settings.addr.into(), + prefix: x.into(), + }) + .collect(); + + diesel::insert_into(allow_import::table) + .values(to_insert) + .execute_async(&conn) + .await + .map_err(|e: diesel::result::Error| { + let message = "error while creating bgp allowed import configuration"; + match e { + diesel::result::Error::DatabaseError(kind, _) => { + match kind { + diesel::result::DatabaseErrorKind::UniqueViolation => { + err.bail(Error::conflict("allowed import configuration conflicts with an existing configuration")) + }, + diesel::result::DatabaseErrorKind::NotNullViolation => { + err.bail(Error::invalid_request("a required field is not populated")) + }, + _ => err.bail(Error::internal_error(message)), + } + }, + _ => err.bail(Error::internal_error(message)), + } + })?; + + true + }, + }; + + // create export list if enabled + let allow_export_list_active = match new_settings.allowed_export.clone() { + ImportExportPolicy::NoFiltering => false, + ImportExportPolicy::Allow(prefixes) => { + use db::schema::switch_port_settings_bgp_peer_config_allow_export as allow_export; + let to_insert: Vec = prefixes + .clone() + .into_iter() + .map(|x| SwitchPortBgpPeerConfigAllowExport { + port_settings_id, + interface_name: new_settings.interface_name.clone(), + addr: new_settings.addr.into(), + prefix: x.into(), + }) + .collect(); + + diesel::insert_into(allow_export::table) + .values(to_insert) + .execute_async(&conn) + .await + .map_err(|e: diesel::result::Error| { + let message = "error while creating bgp allowed export configuration"; + match e { + diesel::result::Error::DatabaseError(kind, _) => { + match kind { + diesel::result::DatabaseErrorKind::UniqueViolation => { + err.bail(Error::conflict("allowed export configuration conflicts with an existing configuration")) + }, + diesel::result::DatabaseErrorKind::NotNullViolation => { + err.bail(Error::invalid_request("a required field is not populated")) + }, + _ => err.bail(Error::internal_error(message)), + } + }, + _ => err.bail(Error::internal_error(message)), + } + })?; + + true + }, + }; + + // create communities + if !new_settings.communities.is_empty() { + use db::schema::switch_port_settings_bgp_peer_config_communities as peer_communities; + let to_insert: Vec = new_settings + .communities + .clone() + .into_iter() + .map(|x| SwitchPortBgpPeerConfigCommunity { + port_settings_id, + interface_name: new_settings.interface_name.clone(), + addr: new_settings.addr.into(), + community: x.into(), + }) + .collect(); + + diesel::insert_into(peer_communities::table) + .values(to_insert) + .execute_async(&conn) + .await + .map_err(|e: diesel::result::Error| { + let message = "error while creating bgp communities for peer"; + match e { + diesel::result::Error::DatabaseError(kind, _) => { + match kind { + diesel::result::DatabaseErrorKind::UniqueViolation => { + err.bail(Error::conflict("peer communities configuration conflicts with an existing configuration")) + }, + diesel::result::DatabaseErrorKind::NotNullViolation => { + err.bail(Error::invalid_request("a required field is not populated")) + }, + _ => err.bail(Error::internal_error(message)), + } + }, + _ => err.bail(Error::internal_error(message)), + } + })?; + + } + + let bgp_peer_config = SwitchPortBgpPeerConfig { + port_settings_id, + bgp_config_id, + interface_name: new_settings.interface_name, + addr: new_settings.addr.into(), + hold_time: new_settings.hold_time.into(), + idle_hold_time: new_settings.idle_hold_time.into(), + delay_open: new_settings.delay_open.into(), + connect_retry: new_settings.connect_retry.into(), + keepalive: new_settings.keepalive.into(), + remote_asn: new_settings.remote_asn.map(Into::into), + min_ttl: new_settings.min_ttl.map(Into::into), + md5_auth_key: new_settings.md5_auth_key, + multi_exit_discriminator: new_settings.multi_exit_discriminator.map(Into::into), + local_pref: new_settings.local_pref.map(Into::into), + enforce_first_as: new_settings.enforce_first_as, + allow_import_list_active, + allow_export_list_active, + vlan_id: new_settings.vlan_id.map(Into::into), + }; + + let peer = diesel::insert_into(bgp_peer_config::table) + .values(bgp_peer_config) + .returning(SwitchPortBgpPeerConfig::as_returning()) + .get_result_async(&conn) + .await + .map_err(|e: diesel::result::Error| { + let message = "error while adding bgp peer to interface"; + match e { + diesel::result::Error::DatabaseError(kind, _) => { + match kind { + diesel::result::DatabaseErrorKind::UniqueViolation => { + err.bail(Error::conflict("bgp peer configuration conflicts with an existing configuration")) + }, + diesel::result::DatabaseErrorKind::NotNullViolation => { + err.bail(Error::invalid_request("a required field is not populated")) + }, + _ => err.bail(Error::internal_error(message)), + } + }, + _ => err.bail(Error::internal_error(message)), + } + })?; + + let config = BgpPeerConfig { + port_settings_id, + bgp_config_id, + interface_name: peer.interface_name, + addr: peer.addr, + hold_time: peer.hold_time, + idle_hold_time: peer.idle_hold_time, + delay_open: peer.delay_open, + connect_retry: peer.connect_retry, + keepalive: peer.keepalive, + remote_asn: peer.remote_asn, + min_ttl: peer.min_ttl, + md5_auth_key: peer.md5_auth_key, + multi_exit_discriminator: peer.multi_exit_discriminator, + local_pref: peer.local_pref, + enforce_first_as: peer.enforce_first_as, + allowed_import: new_settings.allowed_import, + allowed_export: new_settings.allowed_export, + communities: new_settings.communities, + vlan_id: peer.vlan_id, + }; + + Ok(config) + } + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_interface_bgp_peer_add failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while adding bgp peer to interface", + ) + } + } + }) + } + + pub async fn switch_port_configuration_bgp_peer_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: BgpPeer, + ) -> DeleteResult { + use db::schema::switch_port_settings_bgp_peer_config as bgp_peer_config; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_interface_bgp_peer_remove", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let settings_to_remove = bgp_peer.clone(); + let err = err.clone(); + + async move { + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up configuration for interface bgp peer" + )) + }, + } + })?; + + // delete allowed import / export + // PRIMARY KEY (port_settings_id, interface_name, addr, prefix) + use db::schema::switch_port_settings_bgp_peer_config_allow_import as allow_import; + diesel::delete(allow_import::table) + .filter(allow_import::port_settings_id.eq(port_settings_id)) + .filter(allow_import::interface_name.eq(settings_to_remove.interface_name.clone())) + .filter(allow_import::addr.eq(IpNetwork::from(settings_to_remove.addr))) + .execute_async(&conn) + .await + .map_err(|e| { + let message = "error while deleting import list for bgp peer"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + + use db::schema::switch_port_settings_bgp_peer_config_allow_export as allow_export; + diesel::delete(allow_export::table) + .filter(allow_export::port_settings_id.eq(port_settings_id)) + .filter(allow_export::interface_name.eq(settings_to_remove.interface_name.clone())) + .filter(allow_export::addr.eq(IpNetwork::from(settings_to_remove.addr))) + .execute_async(&conn) + .await + .map_err(|e| { + let message = "error while deleting export list for bgp peer"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + + // delete communities + // PRIMARY KEY (port_settings_id, interface_name, addr, community) + use db::schema::switch_port_settings_bgp_peer_config_communities as peer_communities; + diesel::delete(peer_communities::table) + .filter(peer_communities::port_settings_id.eq(port_settings_id)) + .filter(peer_communities::interface_name.eq(settings_to_remove.interface_name.clone())) + .filter(peer_communities::addr.eq(IpNetwork::from(settings_to_remove.addr))) + .execute_async(&conn) + .await + .map_err(|e| { + let message = "error while deleting communities for bgp peer"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + + // delete bgp peer config + // PRIMARY KEY (port_settings_id, interface_name, addr) + diesel::delete(bgp_peer_config::table) + .filter(bgp_peer_config::addr.eq(IpNetwork::from(settings_to_remove.addr))) + .filter(bgp_peer_config::port_settings_id.eq(port_settings_id)) + .filter(bgp_peer_config::interface_name.eq(settings_to_remove.interface_name)) + .execute_async(&conn) + .await + .map_err(|e| { + let message = "error while deleting bgp peer"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + + Ok(()) + + } + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_interface_bgp_peer_remove failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while removing bgp peer from interface", + ) + } + } + }) } // switch ports diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index eeae69f6a4e..9d85dabcc83 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -16,10 +16,12 @@ use nexus_db_model::SwitchPortRouteConfig; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; +use nexus_db_queries::db::datastore::BgpPeerConfig; use nexus_db_queries::db::datastore::UpdatePrecondition; use nexus_db_queries::db::model::{SwitchPort, SwitchPortSettings}; use nexus_db_queries::db::DataStore; use omicron_common::api::external::http_pagination::PaginatedBy; +use omicron_common::api::external::BgpPeer; use omicron_common::api::external::SwitchLocation; use omicron_common::api::external::{ self, CreateResult, DataPageParams, DeleteResult, Error, ListResultVec, @@ -288,6 +290,49 @@ impl super::Nexus { .await } + pub(crate) async fn switch_port_configuration_bgp_peer_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + ) -> ListResultVec { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_list(opctx, configuration) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: BgpPeer, + ) -> CreateResult { + opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_add( + opctx, + configuration, + bgp_peer, + ) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: BgpPeer, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_remove( + opctx, + configuration, + bgp_peer, + ) + .await + } + async fn switch_port_create( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 69a09f2d9be..45b0f9621f2 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -40,10 +40,7 @@ use nexus_db_queries::db::identity::Resource; use nexus_db_queries::db::lookup::ImageLookup; use nexus_db_queries::db::lookup::ImageParentLookup; use nexus_db_queries::db::model::Name; -use nexus_types::external_api::{ - params::BgpPeerConfig, - shared::{BfdStatus, ProbeInfo}, -}; +use nexus_types::external_api::shared::{BfdStatus, ProbeInfo}; use omicron_common::api::external::AddressLot; use omicron_common::api::external::AddressLotCreateResponse; use omicron_common::api::external::AggregateBgpMessageHistory; @@ -279,51 +276,49 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register(networking_switch_port_configuration_delete)?; // /v1/system/networking/switch-port-configuration/{name_or_id}/geometry - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_geometry_view)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_geometry_set)?; // /v1/system/networking/switch-port-configuration/{name_or_id}/link - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_create)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_delete)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_view)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_list)?; // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/address - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_address_add)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_address_remove)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_address_list)?; // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/route - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_route_add)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_route_remove)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_route_list)?; // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/bgp-peer - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_bgp_peer_add)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_bgp_peer_remove)?; - // TODO: Levon - test + // TODO: Levon - add tests api.register(networking_switch_port_configuration_bgp_peer_list)?; api.register(networking_switch_port_list)?; api.register(networking_switch_port_status)?; api.register(networking_switch_port_apply_settings)?; api.register(networking_switch_port_clear_settings)?; - // TODO: Levon - test - // api.register(networking_switch_port_view_settings)?; api.register(networking_bgp_config_create)?; api.register(networking_bgp_config_list)?; @@ -4190,7 +4185,7 @@ async fn networking_switch_port_configuration_route_add( .await } -/// Remove address from an interface configuration +/// Remove route from an interface configuration #[endpoint { method = POST, path ="/v1/system/networking/switch-port-configuration/{configuration}/route/remove", @@ -4233,15 +4228,18 @@ async fn networking_switch_port_configuration_route_remove( async fn networking_switch_port_configuration_bgp_peer_list( rqctx: RequestContext, path_params: Path, -) -> Result, HttpError> { +) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let configuration = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("list interface routes") + let settings = nexus + .switch_port_configuration_bgp_peer_list(&opctx, configuration) + .await?; + + Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) }; apictx .context @@ -4264,11 +4262,18 @@ async fn networking_switch_port_configuration_bgp_peer_add( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let configuration = path_params.into_inner().configuration; + let bgp_peer = bgp_peer.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("add interface route") + let settings = nexus + .switch_port_configuration_bgp_peer_add( + &opctx, + configuration, + bgp_peer, + ) + .await?; + Ok(HttpResponseCreated(settings.into())) }; apictx .context @@ -4291,11 +4296,18 @@ async fn networking_switch_port_configuration_bgp_peer_remove( let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let query = path_params.into_inner().configuration; + let configuration = path_params.into_inner().configuration; + let bgp_peer = bgp_peer.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - todo!("remove interface route") + nexus + .switch_port_configuration_bgp_peer_remove( + &opctx, + configuration, + bgp_peer, + ) + .await?; + Ok(HttpResponseDeleted()) }; apictx .context diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index cb77c1648d7..e173f301f37 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1677,6 +1677,13 @@ pub struct OptionalBgpAnnounceSetSelector { pub name_or_id: Option, } +/// Optionally select a BGP Peer by a name or id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct OptionalBgpPeerSelector { + /// A name or id to use when filtering or paginating bgp peers + pub name_or_id: Option, +} + /// Select a BGP announce set by a name or id. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct BgpAnnounceSetSelector { diff --git a/openapi/nexus.json b/openapi/nexus.json index bc6fe94ab90..fd4cc8ec4d0 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7423,7 +7423,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BgpPeerConfig" + "title": "Array_of_BgpPeer", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpPeer" + } } } } @@ -7878,7 +7882,7 @@ "tags": [ "system/networking" ], - "summary": "Remove address from an interface configuration", + "summary": "Remove route from an interface configuration", "operationId": "networking_switch_port_configuration_route_remove", "parameters": [ { From 193bcafad95f825fa69b84660fcc15086ab63785 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 30 Aug 2024 19:34:29 +0000 Subject: [PATCH 24/37] don't require entire peer config for peer removal --- common/src/api/external/mod.rs | 17 +++++++++++ .../src/db/datastore/switch_port.rs | 8 ++--- nexus/src/app/switch_port.rs | 3 +- nexus/src/external_api/http_entrypoints.rs | 4 +-- openapi/nexus.json | 30 ++++++++++++++++++- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index e091d25cd75..ae1cf065c42 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2579,6 +2579,23 @@ pub struct BgpPeer { pub vlan_id: Option, } +/// A BGP peer configuration to remove from an interface +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BgpPeerRemove { + /// The global BGP configuration used for establishing a session with this + /// peer. + pub bgp_config: NameOrId, + + /// The name of interface to peer on. This is relative to the port + /// configuration this BGP peer configuration is a part of. For example this + /// value could be phy0 to refer to a primary physical interface. Or it + /// could be vlan47 to refer to a VLAN interface. + pub interface_name: String, + + /// The address of the host to peer with. + pub addr: IpAddr, +} + /// A base BGP configuration. #[derive( ObjectIdentity, Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq, diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 31948df6e4a..29b3d384410 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -39,9 +39,9 @@ use nexus_db_model::{ use nexus_types::external_api::params; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ - self, BgpPeer, CreateResult, DataPageParams, DeleteResult, Error, - ImportExportPolicy, ListResultVec, LookupResult, NameOrId, ResourceType, - UpdateResult, + self, BgpPeer, BgpPeerRemove, CreateResult, DataPageParams, DeleteResult, + Error, ImportExportPolicy, ListResultVec, LookupResult, NameOrId, + ResourceType, UpdateResult, }; use ref_cast::RefCast; use serde::{Deserialize, Serialize}; @@ -1876,7 +1876,7 @@ impl DataStore { &self, opctx: &OpContext, configuration: NameOrId, - bgp_peer: BgpPeer, + bgp_peer: BgpPeerRemove, ) -> DeleteResult { use db::schema::switch_port_settings_bgp_peer_config as bgp_peer_config; diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 9d85dabcc83..ac368540ead 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -22,6 +22,7 @@ use nexus_db_queries::db::model::{SwitchPort, SwitchPortSettings}; use nexus_db_queries::db::DataStore; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::BgpPeer; +use omicron_common::api::external::BgpPeerRemove; use omicron_common::api::external::SwitchLocation; use omicron_common::api::external::{ self, CreateResult, DataPageParams, DeleteResult, Error, ListResultVec, @@ -321,7 +322,7 @@ impl super::Nexus { &self, opctx: &OpContext, configuration: NameOrId, - bgp_peer: BgpPeer, + bgp_peer: BgpPeerRemove, ) -> DeleteResult { opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; self.db_datastore diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 45b0f9621f2..9b854c7cf60 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -41,7 +41,6 @@ use nexus_db_queries::db::lookup::ImageLookup; use nexus_db_queries::db::lookup::ImageParentLookup; use nexus_db_queries::db::model::Name; use nexus_types::external_api::shared::{BfdStatus, ProbeInfo}; -use omicron_common::api::external::AddressLot; use omicron_common::api::external::AddressLotCreateResponse; use omicron_common::api::external::AggregateBgpMessageHistory; use omicron_common::api::external::BgpAnnounceSet; @@ -79,6 +78,7 @@ use omicron_common::api::external::{ }, SwitchPortAddressConfig, }; +use omicron_common::api::external::{AddressLot, BgpPeerRemove}; use omicron_common::api::external::{AddressLotBlock, SwitchPortRouteConfig}; use omicron_common::bail_unless; use omicron_uuid_kinds::GenericUuid; @@ -4291,7 +4291,7 @@ async fn networking_switch_port_configuration_bgp_peer_add( async fn networking_switch_port_configuration_bgp_peer_remove( rqctx: RequestContext, path_params: Path, - bgp_peer: TypedBody, + bgp_peer: TypedBody, ) -> Result { let apictx = rqctx.context(); let handler = async { diff --git a/openapi/nexus.json b/openapi/nexus.json index fd4cc8ec4d0..38f70e87665 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7511,7 +7511,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BgpPeer" + "$ref": "#/components/schemas/BgpPeerRemove" } } }, @@ -11425,6 +11425,34 @@ "peers" ] }, + "BgpPeerRemove": { + "description": "A BGP peer configuration to remove from an interface", + "type": "object", + "properties": { + "addr": { + "description": "The address of the host to peer with.", + "type": "string", + "format": "ip" + }, + "bgp_config": { + "description": "The global BGP configuration used for establishing a session with this peer.", + "allOf": [ + { + "$ref": "#/components/schemas/NameOrId" + } + ] + }, + "interface_name": { + "description": "The name of interface to peer on. This is relative to the port configuration this BGP peer configuration is a part of. For example this value could be phy0 to refer to a primary physical interface. Or it could be vlan47 to refer to a VLAN interface.", + "type": "string" + } + }, + "required": [ + "addr", + "bgp_config", + "interface_name" + ] + }, "BgpPeerState": { "description": "The current state of a BGP peer.", "oneOf": [ From 1360445273be6e1fe895636a3bc066c6f9fce9fb Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 30 Aug 2024 22:39:51 +0000 Subject: [PATCH 25/37] WIP: bgp import/export/community add and list --- .../src/db/datastore/switch_port.rs | 343 +++++++++++++++- nexus/src/app/switch_port.rs | 162 ++++++++ nexus/src/external_api/http_entrypoints.rs | 369 +++++++++++++++++- nexus/types/src/external_api/params.rs | 30 ++ 4 files changed, 898 insertions(+), 6 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 29b3d384410..313baa3e1ac 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -36,7 +36,9 @@ use nexus_db_model::{ SwitchPortBgpPeerConfigAllowImport, SwitchPortBgpPeerConfigCommunity, SwitchPortGeometry, }; -use nexus_types::external_api::params; +use nexus_types::external_api::params::{ + self, AllowedPrefixAddRemove, BgpCommunityAddRemove, +}; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ self, BgpPeer, BgpPeerRemove, CreateResult, DataPageParams, DeleteResult, @@ -1992,6 +1994,345 @@ impl DataStore { }) } + pub async fn switch_port_configuration_bgp_peer_allow_import_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + ) -> ListResultVec { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings_bgp_peer_config_allow_import as allow_import; + + let dataset = port_settings::table.inner_join( + allow_import::table + .on(allow_import::port_settings_id.eq(port_settings::id)), + ); + + let query = match configuration { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + query + .filter(allow_import::addr.eq(IpNetwork::from(bgp_peer))) + .select(SwitchPortBgpPeerConfigAllowImport::as_select()) + .load_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e: diesel::result::Error| { + let msg = "error while looking up bgp peer allowed import list"; + error!(opctx.log, "{msg}"; "error" => ?e); + Error::internal_error(msg) + }) + } + + pub async fn switch_port_configuration_bgp_peer_allow_import_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + prefix: AllowedPrefixAddRemove, + ) -> CreateResult { + use db::schema::switch_port_settings_bgp_peer_config_allow_import as allow_import; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_bgp_peer_allow_import_add", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let new_settings = prefix.clone(); + let err = err.clone(); + + async move { + + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up switch port configuration for bgp peer" + )) + }, + } + })?; + + let allow_import_config = SwitchPortBgpPeerConfigAllowImport { + port_settings_id, + interface_name: new_settings.interface.to_string(), + addr: bgp_peer.into(), + prefix: new_settings.prefix.into(), + }; + + let config = diesel::insert_into(allow_import::table) + .values(allow_import_config) + .returning(SwitchPortBgpPeerConfigAllowImport::as_returning()) + .get_result_async(&conn) + .await?; + + Ok(config) + } + }) + .await + .map_err(|e| { + let message = "switch_port_configuration_bgp_peer_allow_import_add failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + }, + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error("error while adding prefix to allowed import list") + }, + } + }) + } + + pub async fn switch_port_configuration_bgp_peer_allow_export_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + ) -> ListResultVec { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings_bgp_peer_config_allow_export as allow_export; + + let dataset = port_settings::table.inner_join( + allow_export::table + .on(allow_export::port_settings_id.eq(port_settings::id)), + ); + + let query = match configuration { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + query + .filter(allow_export::addr.eq(IpNetwork::from(bgp_peer))) + .select(SwitchPortBgpPeerConfigAllowExport::as_select()) + .load_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e: diesel::result::Error| { + let msg = "error while looking up bgp peer allowed export list"; + error!(opctx.log, "{msg}"; "error" => ?e); + Error::internal_error(msg) + }) + } + + pub async fn switch_port_configuration_bgp_peer_allow_export_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + prefix: AllowedPrefixAddRemove, + ) -> CreateResult { + use db::schema::switch_port_settings_bgp_peer_config_allow_export as allow_export; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_bgp_peer_allow_export_add", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let new_settings = prefix.clone(); + let err = err.clone(); + + async move { + + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up switch port configuration for bgp peer" + )) + }, + } + })?; + + let allow_export_config = SwitchPortBgpPeerConfigAllowExport { + port_settings_id, + interface_name: new_settings.interface.to_string(), + addr: bgp_peer.into(), + prefix: new_settings.prefix.into(), + }; + + let config = diesel::insert_into(allow_export::table) + .values(allow_export_config) + .returning(SwitchPortBgpPeerConfigAllowExport::as_returning()) + .get_result_async(&conn) + .await?; + + Ok(config) + } + }) + .await + .map_err(|e| { + let message = "switch_port_configuration_bgp_peer_allow_export_add failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + }, + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error("error while adding prefix to allowed export list") + }, + } + }) + } + + pub async fn switch_port_configuration_bgp_peer_community_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + ) -> ListResultVec { + use db::schema::switch_port_settings as port_settings; + use db::schema::switch_port_settings_bgp_peer_config_communities as communities; + + let dataset = port_settings::table.inner_join( + communities::table + .on(communities::port_settings_id.eq(port_settings::id)), + ); + + let query = match configuration { + NameOrId::Id(id) => { + // find port config using port settings id + dataset.filter(port_settings::id.eq(id)).into_boxed() + } + NameOrId::Name(name) => { + // find port config using port settings name + dataset + .filter(port_settings::name.eq(name.to_string())) + .into_boxed() + } + }; + + query + .filter(communities::addr.eq(IpNetwork::from(bgp_peer))) + .select(SwitchPortBgpPeerConfigCommunity::as_select()) + .load_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e: diesel::result::Error| { + let msg = "error while looking up bgp peer allowed export list"; + error!(opctx.log, "{msg}"; "error" => ?e); + Error::internal_error(msg) + }) + } + + pub async fn switch_port_configuration_bgp_peer_community_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + prefix: BgpCommunityAddRemove, + ) -> CreateResult { + use db::schema::switch_port_settings_bgp_peer_config_communities as communities; + + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_bgp_peer_community_add", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let new_settings = prefix.clone(); + let err = err.clone(); + + async move { + + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up switch port configuration for bgp peer" + )) + }, + } + })?; + + let community_config = SwitchPortBgpPeerConfigCommunity { + port_settings_id, + interface_name: new_settings.interface.to_string(), + addr: bgp_peer.into(), + community: new_settings.community.into(), + }; + + let config = diesel::insert_into(communities::table) + .values(community_config) + .returning(SwitchPortBgpPeerConfigCommunity::as_returning()) + .get_result_async(&conn) + .await?; + + Ok(config) + } + }) + .await + .map_err(|e| { + let message = "switch_port_configuration_bgp_peer_community_add failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + }, + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error("error while adding community to bgp peer") + }, + } + }) + } + // switch ports pub async fn switch_port_create( diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index ac368540ead..ad5cc901987 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -9,6 +9,9 @@ use dpd_client::types::LinkId; use dpd_client::types::PortId; use http::StatusCode; use nexus_db_model::SwitchPortAddressConfig; +use nexus_db_model::SwitchPortBgpPeerConfigAllowExport; +use nexus_db_model::SwitchPortBgpPeerConfigAllowImport; +use nexus_db_model::SwitchPortBgpPeerConfigCommunity; use nexus_db_model::SwitchPortConfig; use nexus_db_model::SwitchPortGeometry; use nexus_db_model::SwitchPortLinkConfig; @@ -20,6 +23,8 @@ use nexus_db_queries::db::datastore::BgpPeerConfig; use nexus_db_queries::db::datastore::UpdatePrecondition; use nexus_db_queries::db::model::{SwitchPort, SwitchPortSettings}; use nexus_db_queries::db::DataStore; +use nexus_types::external_api::params::AllowedPrefixAddRemove; +use nexus_types::external_api::params::BgpCommunityAddRemove; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::BgpPeer; use omicron_common::api::external::BgpPeerRemove; @@ -28,6 +33,7 @@ use omicron_common::api::external::{ self, CreateResult, DataPageParams, DeleteResult, Error, ListResultVec, LookupResult, Name, NameOrId, UpdateResult, }; +use std::net::IpAddr; use std::sync::Arc; use uuid::Uuid; @@ -334,6 +340,162 @@ impl super::Nexus { .await } + pub(crate) async fn switch_port_configuration_bgp_peer_allow_import_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + ) -> ListResultVec { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_allow_import_list( + opctx, + configuration, + bgp_peer, + ) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_allow_import_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + prefix: AllowedPrefixAddRemove, + ) -> CreateResult { + opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_allow_import_add( + opctx, + configuration, + bgp_peer, + prefix, + ) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_allow_import_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + prefix: AllowedPrefixAddRemove, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_allow_import_remove( + opctx, + configuration, + bgp_peer, + prefix, + ) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_allow_export_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + ) -> ListResultVec { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_allow_export_list( + opctx, + configuration, + bgp_peer, + ) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_allow_export_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + prefix: AllowedPrefixAddRemove, + ) -> CreateResult { + opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_allow_export_add( + opctx, + configuration, + bgp_peer, + prefix, + ) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_allow_export_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + prefix: AllowedPrefixAddRemove, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_allow_export_remove( + opctx, + configuration, + bgp_peer, + prefix, + ) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_community_list( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + ) -> ListResultVec { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_community_list( + opctx, + configuration, + bgp_peer, + ) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_community_add( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + community: BgpCommunityAddRemove, + ) -> CreateResult { + opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_community_add( + opctx, + configuration, + bgp_peer, + community, + ) + .await + } + + pub(crate) async fn switch_port_configuration_bgp_peer_community_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + community: BgpCommunityAddRemove, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, &authz::FLEET).await?; + self.db_datastore + .switch_port_configuration_bgp_peer_community_remove( + opctx, + configuration, + bgp_peer, + community, + ) + .await + } + async fn switch_port_create( &self, opctx: &OpContext, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 9b854c7cf60..ecb6cb9898f 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -275,13 +275,11 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register(networking_switch_port_configuration_create)?; api.register(networking_switch_port_configuration_delete)?; - // /v1/system/networking/switch-port-configuration/{name_or_id}/geometry // TODO: Levon - add tests api.register(networking_switch_port_configuration_geometry_view)?; // TODO: Levon - add tests api.register(networking_switch_port_configuration_geometry_set)?; - // /v1/system/networking/switch-port-configuration/{name_or_id}/link // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_create)?; // TODO: Levon - add tests @@ -291,7 +289,6 @@ pub(crate) fn external_api() -> NexusApiDescription { // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_list)?; - // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/address // TODO: Levon - add tests api.register(networking_switch_port_configuration_address_add)?; // TODO: Levon - add tests @@ -299,7 +296,6 @@ pub(crate) fn external_api() -> NexusApiDescription { // TODO: Levon - add tests api.register(networking_switch_port_configuration_address_list)?; - // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/route // TODO: Levon - add tests api.register(networking_switch_port_configuration_route_add)?; // TODO: Levon - add tests @@ -307,7 +303,6 @@ pub(crate) fn external_api() -> NexusApiDescription { // TODO: Levon - add tests api.register(networking_switch_port_configuration_route_list)?; - // /v1/system/networking/switch-port-configuration/{name_or_id}/interface/{interface}/bgp-peer // TODO: Levon - add tests api.register(networking_switch_port_configuration_bgp_peer_add)?; // TODO: Levon - add tests @@ -315,6 +310,43 @@ pub(crate) fn external_api() -> NexusApiDescription { // TODO: Levon - add tests api.register(networking_switch_port_configuration_bgp_peer_list)?; + // TODO: Levon - add tests + api.register( + networking_switch_port_configuration_bgp_peer_allow_import_add, + )?; + // TODO: Levon - add tests + api.register( + networking_switch_port_configuration_bgp_peer_allow_import_remove, + )?; + // TODO: Levon - add tests + api.register( + networking_switch_port_configuration_bgp_peer_allow_import_list, + )?; + + api.register( + networking_switch_port_configuration_bgp_peer_allow_export_add, + )?; + // TODO: Levon - add tests + api.register( + networking_switch_port_configuration_bgp_peer_allow_export_remove, + )?; + // TODO: Levon - add tests + api.register( + networking_switch_port_configuration_bgp_peer_allow_export_list, + )?; + + api.register( + networking_switch_port_configuration_bgp_peer_community_add, + )?; + // TODO: Levon - add tests + api.register( + networking_switch_port_configuration_bgp_peer_community_remove, + )?; + // TODO: Levon - add tests + api.register( + networking_switch_port_configuration_bgp_peer_community_list, + )?; + api.register(networking_switch_port_list)?; api.register(networking_switch_port_status)?; api.register(networking_switch_port_apply_settings)?; @@ -4316,6 +4348,333 @@ async fn networking_switch_port_configuration_bgp_peer_remove( .await } +/// List prefixes allowed to be imported by a given bgp peer +#[endpoint { + method = GET, + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-import", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_bgp_peer_allow_import_list( + rqctx: RequestContext, + path_params: Path, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortSettingsBgpPeerInfoSelector { + configuration, + bgp_peer, + } = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus + .switch_port_configuration_bgp_peer_allow_import_list( + &opctx, + configuration, + bgp_peer, + ) + .await?; + Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Add prefix to bgp peer allowed import list +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-import/add", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_bgp_peer_allow_import_add( + rqctx: RequestContext, + path_params: Path, + prefix: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortSettingsBgpPeerInfoSelector { + configuration, + bgp_peer, + } = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus + .switch_port_configuration_bgp_peer_allow_import_add( + &opctx, + configuration, + bgp_peer, + prefix.into_inner(), + ) + .await?; + Ok(HttpResponseCreated(settings.into())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Remove prefix from bgp peer allowed import list +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-import/remove", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_bgp_peer_allow_import_remove( + rqctx: RequestContext, + path_params: Path, + prefix: TypedBody, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortSettingsBgpPeerInfoSelector { + configuration, + bgp_peer, + } = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + nexus + .switch_port_configuration_bgp_peer_allow_import_remove( + &opctx, + configuration, + bgp_peer, + prefix.into_inner(), + ) + .await?; + Ok(HttpResponseDeleted()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// List addresses assigned to a provided interface configuration +#[endpoint { + method = GET, + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-export", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_bgp_peer_allow_export_list( + rqctx: RequestContext, + path_params: Path, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortSettingsBgpPeerInfoSelector { + configuration, + bgp_peer, + } = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus + .switch_port_configuration_bgp_peer_allow_export_list( + &opctx, + configuration, + bgp_peer, + ) + .await?; + Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Add address to an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-export/add", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_bgp_peer_allow_export_add( + rqctx: RequestContext, + path_params: Path, + prefix: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortSettingsBgpPeerInfoSelector { + configuration, + bgp_peer, + } = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus + .switch_port_configuration_bgp_peer_allow_export_add( + &opctx, + configuration, + bgp_peer, + prefix.into_inner(), + ) + .await?; + Ok(HttpResponseCreated(settings.into())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Remove address from an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-export/remove", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_bgp_peer_allow_export_remove( + rqctx: RequestContext, + path_params: Path, + prefix: TypedBody, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortSettingsBgpPeerInfoSelector { + configuration, + bgp_peer, + } = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + nexus + .switch_port_configuration_bgp_peer_allow_export_remove( + &opctx, + configuration, + bgp_peer, + prefix.into_inner(), + ) + .await?; + Ok(HttpResponseDeleted()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// List addresses assigned to a provided interface configuration +#[endpoint { + method = GET, + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/community", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_bgp_peer_community_list( + rqctx: RequestContext, + path_params: Path, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortSettingsBgpPeerInfoSelector { + configuration, + bgp_peer, + } = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus + .switch_port_configuration_bgp_peer_community_list( + &opctx, + configuration, + bgp_peer, + ) + .await?; + Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Add address to an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/community/add", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_bgp_peer_community_add( + rqctx: RequestContext, + path_params: Path, + community: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortSettingsBgpPeerInfoSelector { + configuration, + bgp_peer, + } = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + let settings = nexus + .switch_port_configuration_bgp_peer_community_add( + &opctx, + configuration, + bgp_peer, + community.into_inner(), + ) + .await?; + Ok(HttpResponseCreated(settings.into())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Remove address from an interface configuration +#[endpoint { + method = POST, + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/community/remove", + tags = ["system/networking"], +}] +async fn networking_switch_port_configuration_bgp_peer_community_remove( + rqctx: RequestContext, + path_params: Path, + community: TypedBody, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortSettingsBgpPeerInfoSelector { + configuration, + bgp_peer, + } = path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + + nexus + .switch_port_configuration_bgp_peer_community_remove( + &opctx, + configuration, + bgp_peer, + community.into_inner(), + ) + .await?; + Ok(HttpResponseDeleted()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + /// List switch ports #[endpoint { method = GET, diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index e173f301f37..fb8abafdf72 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1641,6 +1641,26 @@ pub struct RouteAddRemove { pub local_pref: Option, } +/// A prefix allowed to be imported or exported by a bgp peer +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AllowedPrefixAddRemove { + /// The interface the peer is configured on + pub interface: Name, + + /// The allowed prefix to add or remove + pub prefix: IpNet, +} + +/// A community to be added to or removed from a bgp peer +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpCommunityAddRemove { + /// The interface the peer is configured on + pub interface: Name, + + /// The community to add or remove + pub community: u32, +} + /// Select a BGP config by a name or id. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct BgpConfigSelector { @@ -1833,6 +1853,16 @@ pub struct SwitchPortSettingsInfoSelector { pub configuration: NameOrId, } +/// Select a port settings info object by name or id and peer by address. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortSettingsBgpPeerInfoSelector { + /// A name or id to use when selecting a switch port configuration. + pub configuration: NameOrId, + + /// An address identifying a configured bgp peer. + pub bgp_peer: IpAddr, +} + /// Select a link settings info object by port settings name and link name. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct SwitchPortSettingsLinkInfoSelector { From a392a8010cf11a763ace58ec5f16e8199a8b99fa Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 30 Aug 2024 23:23:27 +0000 Subject: [PATCH 26/37] WIP: finish roughing out bgp import/export/community endpoints --- common/src/api/external/mod.rs | 26 ++ nexus/db-model/src/switch_port.rs | 35 ++- .../src/db/datastore/switch_port.rs | 236 +++++++++++++++++- nexus/src/external_api/http_entrypoints.rs | 32 +-- 4 files changed, 312 insertions(+), 17 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index ae1cf065c42..13f72c9a903 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2596,6 +2596,32 @@ pub struct BgpPeerRemove { pub addr: IpAddr, } +/// A BGP allowed prefix entry +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct BgpAllowedPrefix { + /// Parent switch port configuration + pub port_settings_id: Uuid, + /// Interface peer is reachable on + pub interface_name: String, + /// Peer Address + pub addr: oxnet::IpNet, + /// Allowed Prefix + pub prefix: oxnet::IpNet, +} + +/// A BGP community +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct BgpCommunity { + /// Parent switch port configuration + pub port_settings_id: Uuid, + /// Interface peer is reachable on + pub interface_name: String, + /// Peer Address + pub addr: oxnet::IpNet, + /// Community + pub community: u32, +} + /// A base BGP configuration. #[derive( ObjectIdentity, Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq, diff --git a/nexus/db-model/src/switch_port.rs b/nexus/db-model/src/switch_port.rs index 09f1327be28..3d551a1249d 100644 --- a/nexus/db-model/src/switch_port.rs +++ b/nexus/db-model/src/switch_port.rs @@ -20,7 +20,7 @@ use diesel::AsChangeset; use ipnetwork::IpNetwork; use nexus_types::external_api::params; use nexus_types::identity::Resource; -use omicron_common::api::external; +use omicron_common::api::external::{self, BgpAllowedPrefix, BgpCommunity}; use omicron_common::api::external::{BgpPeer, ImportExportPolicy}; use omicron_common::api::internal::shared::{PortFec, PortSpeed}; use serde::{Deserialize, Serialize}; @@ -638,6 +638,17 @@ pub struct SwitchPortBgpPeerConfigCommunity { pub community: SqlU32, } +impl Into for SwitchPortBgpPeerConfigCommunity { + fn into(self) -> BgpCommunity { + BgpCommunity { + port_settings_id: self.port_settings_id, + interface_name: self.interface_name, + addr: self.addr.into(), + community: self.community.into(), + } + } +} + #[derive( Queryable, Insertable, @@ -660,6 +671,17 @@ pub struct SwitchPortBgpPeerConfigAllowExport { pub prefix: IpNetwork, } +impl Into for SwitchPortBgpPeerConfigAllowExport { + fn into(self) -> BgpAllowedPrefix { + BgpAllowedPrefix { + port_settings_id: self.port_settings_id, + interface_name: self.interface_name, + addr: self.addr.into(), + prefix: self.prefix.into(), + } + } +} + #[derive( Queryable, Insertable, @@ -721,6 +743,17 @@ impl SwitchPortBgpPeerConfig { } } +impl Into for SwitchPortBgpPeerConfigAllowImport { + fn into(self) -> BgpAllowedPrefix { + BgpAllowedPrefix { + port_settings_id: self.port_settings_id, + interface_name: self.interface_name, + addr: self.addr.into(), + prefix: self.prefix.into(), + } + } +} + #[derive( Queryable, Insertable, diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 313baa3e1ac..ddb8a9c380b 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -2107,6 +2107,83 @@ impl DataStore { }) } + pub async fn switch_port_configuration_bgp_peer_allow_import_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + prefix: AllowedPrefixAddRemove, + ) -> DeleteResult { + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_bgp_peer_allow_import_remove", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let settings_to_remove = prefix.clone(); + let err = err.clone(); + + async move { + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up configuration for interface bgp peer" + )) + }, + } + })?; + + // delete allowed import + // PRIMARY KEY (port_settings_id, interface_name, addr, prefix) + use db::schema::switch_port_settings_bgp_peer_config_allow_import as allow_import; + diesel::delete(allow_import::table) + .filter(allow_import::port_settings_id.eq(port_settings_id)) + .filter(allow_import::interface_name.eq(settings_to_remove.interface.to_string())) + .filter(allow_import::addr.eq(IpNetwork::from(bgp_peer))) + .filter(allow_import::prefix.eq(IpNetwork::from(settings_to_remove.prefix))) + .execute_async(&conn) + .await + .map_err(|e| { + let message = "error while deleting import list entry for bgp peer"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + Ok(()) + + } + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_bgp_peer_allow_import_remove failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while removing entry from allow import list", + ) + } + } + }) + } + pub async fn switch_port_configuration_bgp_peer_allow_export_list( &self, opctx: &OpContext, @@ -2220,6 +2297,83 @@ impl DataStore { }) } + pub async fn switch_port_configuration_bgp_peer_allow_export_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + prefix: AllowedPrefixAddRemove, + ) -> DeleteResult { + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_bgp_peer_allow_export_remove", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let settings_to_remove = prefix.clone(); + let err = err.clone(); + + async move { + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up configuration for interface bgp peer" + )) + }, + } + })?; + + // delete allowed export + // PRIMARY KEY (port_settings_id, interface_name, addr, prefix) + use db::schema::switch_port_settings_bgp_peer_config_allow_export as allow_export; + diesel::delete(allow_export::table) + .filter(allow_export::port_settings_id.eq(port_settings_id)) + .filter(allow_export::interface_name.eq(settings_to_remove.interface.to_string())) + .filter(allow_export::addr.eq(IpNetwork::from(bgp_peer))) + .filter(allow_export::prefix.eq(IpNetwork::from(settings_to_remove.prefix))) + .execute_async(&conn) + .await + .map_err(|e| { + let message = "error while deleting export list entry for bgp peer"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + Ok(()) + + } + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_bgp_peer_allow_export_remove failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while removing entry from allow export list", + ) + } + } + }) + } + pub async fn switch_port_configuration_bgp_peer_community_list( &self, opctx: &OpContext, @@ -2264,7 +2418,7 @@ impl DataStore { opctx: &OpContext, configuration: NameOrId, bgp_peer: IpAddr, - prefix: BgpCommunityAddRemove, + community: BgpCommunityAddRemove, ) -> CreateResult { use db::schema::switch_port_settings_bgp_peer_config_communities as communities; @@ -2276,7 +2430,7 @@ impl DataStore { ) .transaction(&conn, |conn| { let parent_configuration = configuration.clone(); - let new_settings = prefix.clone(); + let new_settings = community.clone(); let err = err.clone(); async move { @@ -2333,6 +2487,84 @@ impl DataStore { }) } + pub async fn switch_port_configuration_bgp_peer_community_remove( + &self, + opctx: &OpContext, + configuration: NameOrId, + bgp_peer: IpAddr, + community: BgpCommunityAddRemove, + ) -> DeleteResult { + let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); + + self.transaction_retry_wrapper( + "switch_port_configuration_bgp_peer_community_remove", + ) + .transaction(&conn, |conn| { + let parent_configuration = configuration.clone(); + let settings_to_remove = community.clone(); + let err = err.clone(); + + async move { + // resolve id of port_settings record + let port_settings_id = switch_port_configuration_id(&conn, parent_configuration.clone()) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to lookup configuration with identifier {parent_configuration}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up configuration for interface bgp peer" + )) + }, + } + })?; + + // delete communities + // PRIMARY KEY (port_settings_id, interface_name, addr, community) + use db::schema::switch_port_settings_bgp_peer_config_communities as peer_communities; + diesel::delete(peer_communities::table) + .filter(peer_communities::port_settings_id.eq(port_settings_id)) + .filter(peer_communities::interface_name.eq(settings_to_remove.interface.to_string())) + .filter(peer_communities::addr.eq(IpNetwork::from(bgp_peer))) + .filter(peer_communities::community.eq(SqlU32::from(settings_to_remove.community))) + .execute_async(&conn) + .await + .map_err(|e| { + let message = "error while deleting community entry for bgp peer"; + error!(opctx.log, "{message}"; "error" => ?e); + err.bail(Error::internal_error(message)) + })?; + + Ok(()) + + } + }) + .await + .map_err(|e| { + let message = + "switch_port_configuration_bgp_peer_community_remove failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + } + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error( + "error while removing entry from community list", + ) + } + } + }) + } + // switch ports pub async fn switch_port_create( diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index ecb6cb9898f..0e7efa5dcc3 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -41,8 +41,6 @@ use nexus_db_queries::db::lookup::ImageLookup; use nexus_db_queries::db::lookup::ImageParentLookup; use nexus_db_queries::db::model::Name; use nexus_types::external_api::shared::{BfdStatus, ProbeInfo}; -use omicron_common::api::external::AddressLotCreateResponse; -use omicron_common::api::external::AggregateBgpMessageHistory; use omicron_common::api::external::BgpAnnounceSet; use omicron_common::api::external::BgpAnnouncement; use omicron_common::api::external::BgpConfig; @@ -80,6 +78,10 @@ use omicron_common::api::external::{ }; use omicron_common::api::external::{AddressLot, BgpPeerRemove}; use omicron_common::api::external::{AddressLotBlock, SwitchPortRouteConfig}; +use omicron_common::api::external::{ + AddressLotCreateResponse, BgpAllowedPrefix, +}; +use omicron_common::api::external::{AggregateBgpMessageHistory, BgpCommunity}; use omicron_common::bail_unless; use omicron_uuid_kinds::GenericUuid; use parse_display::Display; @@ -4357,7 +4359,7 @@ async fn networking_switch_port_configuration_bgp_peer_remove( async fn networking_switch_port_configuration_bgp_peer_allow_import_list( rqctx: RequestContext, path_params: Path, -) -> Result>, HttpError> { +) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4374,6 +4376,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_list( bgp_peer, ) .await?; + Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) }; apictx @@ -4393,7 +4396,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_add( rqctx: RequestContext, path_params: Path, prefix: TypedBody, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4457,7 +4460,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_remove( .await } -/// List addresses assigned to a provided interface configuration +/// List prefixes allowed to be exported by a given bgp peer #[endpoint { method = GET, path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-export", @@ -4466,7 +4469,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_remove( async fn networking_switch_port_configuration_bgp_peer_allow_export_list( rqctx: RequestContext, path_params: Path, -) -> Result>, HttpError> { +) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4483,6 +4486,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_list( bgp_peer, ) .await?; + Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) }; apictx @@ -4492,7 +4496,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_list( .await } -/// Add address to an interface configuration +/// Add prefix to bgp peer allowed export list #[endpoint { method = POST, path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-export/add", @@ -4502,7 +4506,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_add( rqctx: RequestContext, path_params: Path, prefix: TypedBody, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4529,7 +4533,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_add( .await } -/// Remove address from an interface configuration +/// Remove prefix from bgp peer allowed export list #[endpoint { method = POST, path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-export/remove", @@ -4566,7 +4570,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_remove( .await } -/// List addresses assigned to a provided interface configuration +/// List communities assigned to a bgp peer #[endpoint { method = GET, path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/community", @@ -4575,7 +4579,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_remove( async fn networking_switch_port_configuration_bgp_peer_community_list( rqctx: RequestContext, path_params: Path, -) -> Result>, HttpError> { +) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4601,7 +4605,7 @@ async fn networking_switch_port_configuration_bgp_peer_community_list( .await } -/// Add address to an interface configuration +/// Add community to bgp peer #[endpoint { method = POST, path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/community/add", @@ -4611,7 +4615,7 @@ async fn networking_switch_port_configuration_bgp_peer_community_add( rqctx: RequestContext, path_params: Path, community: TypedBody, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4638,7 +4642,7 @@ async fn networking_switch_port_configuration_bgp_peer_community_add( .await } -/// Remove address from an interface configuration +/// Remove community from bgp peer #[endpoint { method = POST, path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/community/remove", From a1a70fdbc4003f274f7b7f5c21c0deaee12eb46e Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Sat, 31 Aug 2024 02:15:20 +0000 Subject: [PATCH 27/37] WIP: breakout bgp peer import/export/communities --- common/src/api/external/mod.rs | 65 +- nexus/db-model/src/switch_port.rs | 34 +- .../src/db/datastore/switch_port.rs | 258 +------ nexus/src/app/rack.rs | 6 +- nexus/src/app/switch_port.rs | 6 +- nexus/src/external_api/http_entrypoints.rs | 132 ++-- nexus/tests/integration_tests/switch_port.rs | 6 +- nexus/tests/output/nexus_tags.txt | 9 + .../output/uncovered-authz-endpoints.txt | 9 + nexus/types/src/external_api/params.rs | 21 +- openapi/nexus.json | 682 +++++++++++++++++- 11 files changed, 883 insertions(+), 345 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 13f72c9a903..59c0513286c 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2238,7 +2238,7 @@ pub struct SwitchPortSettingsView { pub routes: Vec, /// BGP peer settings. - pub bgp_peers: Vec, + pub bgp_peers: Vec, /// Layer 3 IP address settings. pub addresses: Vec, @@ -2517,7 +2517,7 @@ pub struct SwitchPortBgpPeerConfig { /// parameter is a reference to global BGP parameters. The `interface_name` /// indicates what interface the peer should be contacted on. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BgpPeer { +pub struct BgpPeerCombined { /// The global BGP configuration used for establishing a session with this /// peer. pub bgp_config: NameOrId, @@ -2579,6 +2579,67 @@ pub struct BgpPeer { pub vlan_id: Option, } +/// The information required to configure a BGP peer. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BgpPeer { + /// The global BGP configuration used for establishing a session with this + /// peer. + pub bgp_config: NameOrId, + + /// The name of interface to peer on. This is relative to the port + /// configuration this BGP peer configuration is a part of. For example this + /// value could be phy0 to refer to a primary physical interface. Or it + /// could be vlan47 to refer to a VLAN interface. + pub interface_name: String, + + /// The address of th e host to peer with. + pub addr: oxnet::IpNet, + + /// How long to hold peer connections between keepalives (seconds). + pub hold_time: u32, + + /// How long to hold a peer in idle before attempting a new session + /// (seconds). + pub idle_hold_time: u32, + + /// How long to delay sending an open request after establishing a TCP + /// session (seconds). + pub delay_open: u32, + + /// How long to to wait between TCP connection retries (seconds). + pub connect_retry: u32, + + /// How often to send keepalive requests (seconds). + pub keepalive: u32, + + /// Require that a peer has a specified ASN. + pub remote_asn: Option, + + /// Require messages from a peer have a minimum IP time to live field. + pub min_ttl: Option, + + /// Use the given key for TCP-MD5 authentication with the peer. + pub md5_auth_key: Option, + + /// Apply the provided multi-exit discriminator (MED) updates sent to the peer. + pub multi_exit_discriminator: Option, + + /// Apply a local preference to routes received from this peer. + pub local_pref: Option, + + /// Enforce that the first AS in paths received from this peer is the peer's AS. + pub enforce_first_as: bool, + + /// Enable import policies + pub allow_import_list_active: bool, + + /// Enable export policies + pub allow_export_list_active: bool, + + /// Associate a VLAN ID with a peer. + pub vlan_id: Option, +} + /// A BGP peer configuration to remove from an interface #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct BgpPeerRemove { diff --git a/nexus/db-model/src/switch_port.rs b/nexus/db-model/src/switch_port.rs index 3d551a1249d..ee57a3066fd 100644 --- a/nexus/db-model/src/switch_port.rs +++ b/nexus/db-model/src/switch_port.rs @@ -20,8 +20,10 @@ use diesel::AsChangeset; use ipnetwork::IpNetwork; use nexus_types::external_api::params; use nexus_types::identity::Resource; -use omicron_common::api::external::{self, BgpAllowedPrefix, BgpCommunity}; -use omicron_common::api::external::{BgpPeer, ImportExportPolicy}; +use omicron_common::api::external::{ + self, BgpAllowedPrefix, BgpCommunity, NameOrId, +}; +use omicron_common::api::external::{BgpPeerCombined, ImportExportPolicy}; use omicron_common::api::internal::shared::{PortFec, PortSpeed}; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -620,6 +622,32 @@ pub struct SwitchPortBgpPeerConfig { pub vlan_id: Option, } +impl Into for SwitchPortBgpPeerConfig { + fn into(self) -> external::BgpPeer { + external::BgpPeer { + bgp_config: NameOrId::Id(self.bgp_config_id), + interface_name: self.interface_name, + addr: self.addr.into(), + hold_time: self.hold_time.into(), + idle_hold_time: self.idle_hold_time.into(), + delay_open: self.delay_open.into(), + connect_retry: self.connect_retry.into(), + keepalive: self.keepalive.into(), + remote_asn: self.remote_asn.map(Into::into), + min_ttl: self.min_ttl.map(Into::into), + md5_auth_key: self.md5_auth_key, + multi_exit_discriminator: self + .multi_exit_discriminator + .map(Into::into), + local_pref: self.local_pref.map(Into::into), + enforce_first_as: self.enforce_first_as, + allow_import_list_active: self.allow_import_list_active, + allow_export_list_active: self.allow_export_list_active, + vlan_id: self.vlan_id.map(Into::into), + } + } +} + #[derive( Queryable, Insertable, @@ -710,7 +738,7 @@ impl SwitchPortBgpPeerConfig { port_settings_id: Uuid, bgp_config_id: Uuid, interface_name: String, - p: &BgpPeer, + p: &BgpPeerCombined, ) -> Self { Self { port_settings_id, diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index ddb8a9c380b..f8b3a61c64a 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -72,9 +72,9 @@ pub struct BgpPeerConfig { pub vlan_id: Option, } -impl Into for BgpPeerConfig { - fn into(self) -> external::BgpPeer { - external::BgpPeer { +impl Into for BgpPeerConfig { + fn into(self) -> external::BgpPeerCombined { + external::BgpPeerCombined { bgp_config: self.bgp_config_id.into(), interface_name: self.interface_name.clone(), addr: self.addr.ip(), @@ -1413,12 +1413,9 @@ impl DataStore { &self, opctx: &OpContext, configuration: NameOrId, - ) -> ListResultVec { + ) -> ListResultVec { use db::schema::switch_port_settings as port_settings; use db::schema::switch_port_settings_bgp_peer_config as bgp_peer_config; - use db::schema::switch_port_settings_bgp_peer_config_allow_export as allow_export; - use db::schema::switch_port_settings_bgp_peer_config_allow_import as allow_import; - use db::schema::switch_port_settings_bgp_peer_config_communities as communities; let conn = self.pool_connection_authorized(opctx).await?; let err = OptionalError::new(); @@ -1466,92 +1463,8 @@ impl DataStore { } })?; - let mut configs: Vec = vec![]; - - for peer in peers { - // get allowed import - let prefixes_to_import: Vec = allow_import::table - .filter(allow_import::addr.eq(peer.addr)) - .filter(allow_import::interface_name.eq(peer.interface_name.clone())) - .filter(allow_import::port_settings_id.eq(peer.port_settings_id)) - .select(allow_import::prefix) - .load_async(&conn) - .await - .map_err(|e| { - let message = "error while looking up bgp peer import configuration"; - error!(opctx.log, "{message}"; "error" => ?e); - err.bail(Error::internal_error(message)) - })?; - - let allowed_import = if prefixes_to_import.is_empty() { - ImportExportPolicy::NoFiltering - } else { - ImportExportPolicy::Allow(prefixes_to_import.into_iter().map(Into::into).collect()) - }; - - // get allowed export - let prefixes_to_export: Vec = allow_export::table - .filter(allow_export::addr.eq(peer.addr)) - .filter(allow_export::interface_name.eq(peer.interface_name.clone())) - .filter(allow_export::port_settings_id.eq(peer.port_settings_id)) - .select(allow_export::prefix) - .load_async(&conn) - .await - .map_err(|e| { - let message = - "error while looking up bgp peer export configuration"; - error!(opctx.log, "{message}"; "error" => ?e); - err.bail(Error::internal_error(message)) - })?; - - let allowed_export = if prefixes_to_export.is_empty() { - ImportExportPolicy::NoFiltering - } else { - ImportExportPolicy::Allow(prefixes_to_export.into_iter().map(Into::into).collect()) - }; - - // get communities - let communities: Vec = communities::table - .filter(communities::addr.eq(peer.addr)) - .filter(communities::interface_name.eq(peer.interface_name.clone())) - .filter(communities::port_settings_id.eq(peer.port_settings_id)) - .select(communities::community) - .load_async(&conn) - .await - .map_err(|e| { - let message = - "error while looking up bgp peer communities"; - error!(opctx.log, "{message}"; "error" => ?e); - err.bail(Error::internal_error(message)) - })?; - - // build config - let config = BgpPeerConfig { - port_settings_id: peer.port_settings_id, - bgp_config_id: peer.bgp_config_id, - interface_name: peer.interface_name, - addr: peer.addr, - hold_time: peer.hold_time, - idle_hold_time: peer.idle_hold_time, - delay_open: peer.delay_open, - connect_retry: peer.connect_retry, - keepalive: peer.keepalive, - remote_asn: peer.remote_asn, - min_ttl: peer.min_ttl, - md5_auth_key: peer.md5_auth_key, - multi_exit_discriminator: peer.multi_exit_discriminator, - local_pref: peer.local_pref, - enforce_first_as: peer.enforce_first_as, - allowed_import, - allowed_export, - communities: communities.into_iter().map(Into::into).collect(), - vlan_id: peer.vlan_id, - }; - // push - configs.push(config); - } - Ok(configs) + Ok(peers) } }) .await @@ -1578,7 +1491,7 @@ impl DataStore { opctx: &OpContext, configuration: NameOrId, bgp_peer: BgpPeer, - ) -> CreateResult { + ) -> CreateResult { use db::schema::bgp_config; use db::schema::switch_port_settings_bgp_peer_config as bgp_peer_config; @@ -1663,129 +1576,6 @@ impl DataStore { } }?; - // create import list if enabled - let allow_import_list_active = match new_settings.allowed_import.clone() { - ImportExportPolicy::NoFiltering => false, - ImportExportPolicy::Allow(prefixes) => { - use db::schema::switch_port_settings_bgp_peer_config_allow_import as allow_import; - let to_insert: Vec = prefixes - .clone() - .into_iter() - .map(|x| SwitchPortBgpPeerConfigAllowImport { - port_settings_id, - interface_name: new_settings.interface_name.clone(), - addr: new_settings.addr.into(), - prefix: x.into(), - }) - .collect(); - - diesel::insert_into(allow_import::table) - .values(to_insert) - .execute_async(&conn) - .await - .map_err(|e: diesel::result::Error| { - let message = "error while creating bgp allowed import configuration"; - match e { - diesel::result::Error::DatabaseError(kind, _) => { - match kind { - diesel::result::DatabaseErrorKind::UniqueViolation => { - err.bail(Error::conflict("allowed import configuration conflicts with an existing configuration")) - }, - diesel::result::DatabaseErrorKind::NotNullViolation => { - err.bail(Error::invalid_request("a required field is not populated")) - }, - _ => err.bail(Error::internal_error(message)), - } - }, - _ => err.bail(Error::internal_error(message)), - } - })?; - - true - }, - }; - - // create export list if enabled - let allow_export_list_active = match new_settings.allowed_export.clone() { - ImportExportPolicy::NoFiltering => false, - ImportExportPolicy::Allow(prefixes) => { - use db::schema::switch_port_settings_bgp_peer_config_allow_export as allow_export; - let to_insert: Vec = prefixes - .clone() - .into_iter() - .map(|x| SwitchPortBgpPeerConfigAllowExport { - port_settings_id, - interface_name: new_settings.interface_name.clone(), - addr: new_settings.addr.into(), - prefix: x.into(), - }) - .collect(); - - diesel::insert_into(allow_export::table) - .values(to_insert) - .execute_async(&conn) - .await - .map_err(|e: diesel::result::Error| { - let message = "error while creating bgp allowed export configuration"; - match e { - diesel::result::Error::DatabaseError(kind, _) => { - match kind { - diesel::result::DatabaseErrorKind::UniqueViolation => { - err.bail(Error::conflict("allowed export configuration conflicts with an existing configuration")) - }, - diesel::result::DatabaseErrorKind::NotNullViolation => { - err.bail(Error::invalid_request("a required field is not populated")) - }, - _ => err.bail(Error::internal_error(message)), - } - }, - _ => err.bail(Error::internal_error(message)), - } - })?; - - true - }, - }; - - // create communities - if !new_settings.communities.is_empty() { - use db::schema::switch_port_settings_bgp_peer_config_communities as peer_communities; - let to_insert: Vec = new_settings - .communities - .clone() - .into_iter() - .map(|x| SwitchPortBgpPeerConfigCommunity { - port_settings_id, - interface_name: new_settings.interface_name.clone(), - addr: new_settings.addr.into(), - community: x.into(), - }) - .collect(); - - diesel::insert_into(peer_communities::table) - .values(to_insert) - .execute_async(&conn) - .await - .map_err(|e: diesel::result::Error| { - let message = "error while creating bgp communities for peer"; - match e { - diesel::result::Error::DatabaseError(kind, _) => { - match kind { - diesel::result::DatabaseErrorKind::UniqueViolation => { - err.bail(Error::conflict("peer communities configuration conflicts with an existing configuration")) - }, - diesel::result::DatabaseErrorKind::NotNullViolation => { - err.bail(Error::invalid_request("a required field is not populated")) - }, - _ => err.bail(Error::internal_error(message)), - } - }, - _ => err.bail(Error::internal_error(message)), - } - })?; - - } - let bgp_peer_config = SwitchPortBgpPeerConfig { port_settings_id, bgp_config_id, @@ -1802,8 +1592,8 @@ impl DataStore { multi_exit_discriminator: new_settings.multi_exit_discriminator.map(Into::into), local_pref: new_settings.local_pref.map(Into::into), enforce_first_as: new_settings.enforce_first_as, - allow_import_list_active, - allow_export_list_active, + allow_import_list_active: new_settings.allow_import_list_active, + allow_export_list_active: new_settings.allow_import_list_active, vlan_id: new_settings.vlan_id.map(Into::into), }; @@ -1830,29 +1620,7 @@ impl DataStore { } })?; - let config = BgpPeerConfig { - port_settings_id, - bgp_config_id, - interface_name: peer.interface_name, - addr: peer.addr, - hold_time: peer.hold_time, - idle_hold_time: peer.idle_hold_time, - delay_open: peer.delay_open, - connect_retry: peer.connect_retry, - keepalive: peer.keepalive, - remote_asn: peer.remote_asn, - min_ttl: peer.min_ttl, - md5_auth_key: peer.md5_auth_key, - multi_exit_discriminator: peer.multi_exit_discriminator, - local_pref: peer.local_pref, - enforce_first_as: peer.enforce_first_as, - allowed_import: new_settings.allowed_import, - allowed_export: new_settings.allowed_export, - communities: new_settings.communities, - vlan_id: peer.vlan_id, - }; - - Ok(config) + Ok(peer) } }) .await @@ -3187,7 +2955,7 @@ async fn do_switch_port_settings_create( .get_results_async(conn) .await?; - let mut peer_by_addr: BTreeMap = + let mut peer_by_addr: BTreeMap = BTreeMap::new(); let mut bgp_peer_config = Vec::new(); @@ -3618,8 +3386,8 @@ mod test { SwitchPortConfigCreate, SwitchPortGeometry, SwitchPortSettingsCreate, }; use omicron_common::api::external::{ - BgpPeer, IdentityMetadataCreateParams, ImportExportPolicy, Name, - NameOrId, + BgpPeerCombined, IdentityMetadataCreateParams, ImportExportPolicy, + Name, NameOrId, }; use omicron_test_utils::dev; use std::collections::HashMap; @@ -3682,7 +3450,7 @@ mod test { bgp_peers: HashMap::from([( "phy0".into(), BgpPeerConfig { - peers: vec![BgpPeer { + peers: vec![BgpPeerCombined { bgp_config: NameOrId::Name( "test-bgp-config".parse().unwrap(), ), diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index d2690eaf133..52c2351641f 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -51,7 +51,7 @@ use nexus_types::identity::Resource; use nexus_types::internal_api::params::DnsRecord; use omicron_common::address::{get_64_subnet, Ipv6Subnet, RACK_PREFIX}; use omicron_common::api::external::AddressLotKind; -use omicron_common::api::external::BgpPeer; +use omicron_common::api::external::BgpPeerCombined; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; use omicron_common::api::external::IdentityMetadataCreateParams; @@ -649,10 +649,10 @@ impl super::Nexus { .routes .insert("phy0".to_string(), RouteConfig { routes }); - let peers: Vec = uplink_config + let peers: Vec = uplink_config .bgp_peers .iter() - .map(|r| BgpPeer { + .map(|r| BgpPeerCombined { bgp_config: NameOrId::Name( format!("as{}", r.asn).parse().unwrap(), ), diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index ad5cc901987..eae653b9d52 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -9,6 +9,7 @@ use dpd_client::types::LinkId; use dpd_client::types::PortId; use http::StatusCode; use nexus_db_model::SwitchPortAddressConfig; +use nexus_db_model::SwitchPortBgpPeerConfig; use nexus_db_model::SwitchPortBgpPeerConfigAllowExport; use nexus_db_model::SwitchPortBgpPeerConfigAllowImport; use nexus_db_model::SwitchPortBgpPeerConfigCommunity; @@ -19,7 +20,6 @@ use nexus_db_model::SwitchPortRouteConfig; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; -use nexus_db_queries::db::datastore::BgpPeerConfig; use nexus_db_queries::db::datastore::UpdatePrecondition; use nexus_db_queries::db::model::{SwitchPort, SwitchPortSettings}; use nexus_db_queries::db::DataStore; @@ -301,7 +301,7 @@ impl super::Nexus { &self, opctx: &OpContext, configuration: NameOrId, - ) -> ListResultVec { + ) -> ListResultVec { opctx.authorize(authz::Action::Read, &authz::FLEET).await?; self.db_datastore .switch_port_configuration_bgp_peer_list(opctx, configuration) @@ -313,7 +313,7 @@ impl super::Nexus { opctx: &OpContext, configuration: NameOrId, bgp_peer: BgpPeer, - ) -> CreateResult { + ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; self.db_datastore .switch_port_configuration_bgp_peer_add( diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 0e7efa5dcc3..8a794ebbecc 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -41,12 +41,10 @@ use nexus_db_queries::db::lookup::ImageLookup; use nexus_db_queries::db::lookup::ImageParentLookup; use nexus_db_queries::db::model::Name; use nexus_types::external_api::shared::{BfdStatus, ProbeInfo}; -use omicron_common::api::external::BgpAnnounceSet; use omicron_common::api::external::BgpAnnouncement; use omicron_common::api::external::BgpConfig; use omicron_common::api::external::BgpExported; use omicron_common::api::external::BgpImportedRouteIpv4; -use omicron_common::api::external::BgpPeer; use omicron_common::api::external::BgpPeerStatus; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Disk; @@ -82,6 +80,7 @@ use omicron_common::api::external::{ AddressLotCreateResponse, BgpAllowedPrefix, }; use omicron_common::api::external::{AggregateBgpMessageHistory, BgpCommunity}; +use omicron_common::api::external::{BgpAnnounceSet, BgpPeer}; use omicron_common::bail_unless; use omicron_uuid_kinds::GenericUuid; use parse_display::Display; @@ -4353,27 +4352,24 @@ async fn networking_switch_port_configuration_bgp_peer_remove( /// List prefixes allowed to be imported by a given bgp peer #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-import", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_import_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, + query: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsBgpPeerInfoSelector { - configuration, - bgp_peer, - } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus .switch_port_configuration_bgp_peer_allow_import_list( &opctx, - configuration, - bgp_peer, + path_params.into_inner().configuration, + query.into_inner().peer_address, ) .await?; @@ -4389,29 +4385,26 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_list( /// Add prefix to bgp peer allowed import list #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-import/add", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/add", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_import_add( rqctx: RequestContext, - path_params: Path, + path_params: Path, prefix: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsBgpPeerInfoSelector { - configuration, - bgp_peer, - } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + let prefix = prefix.into_inner(); let settings = nexus .switch_port_configuration_bgp_peer_allow_import_add( &opctx, - configuration, - bgp_peer, - prefix.into_inner(), + path_params.into_inner().configuration, + prefix.peer_address, + prefix, ) .await?; Ok(HttpResponseCreated(settings.into())) @@ -4426,29 +4419,26 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_add( /// Remove prefix from bgp peer allowed import list #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-import/remove", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/remove", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_import_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, prefix: TypedBody, ) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsBgpPeerInfoSelector { - configuration, - bgp_peer, - } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + let prefix = prefix.into_inner(); nexus .switch_port_configuration_bgp_peer_allow_import_remove( &opctx, - configuration, - bgp_peer, - prefix.into_inner(), + path_params.into_inner().configuration, + prefix.peer_address, + prefix, ) .await?; Ok(HttpResponseDeleted()) @@ -4463,27 +4453,24 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_remove( /// List prefixes allowed to be exported by a given bgp peer #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-export", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_export_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, + query: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsBgpPeerInfoSelector { - configuration, - bgp_peer, - } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus .switch_port_configuration_bgp_peer_allow_export_list( &opctx, - configuration, - bgp_peer, + path_params.into_inner().configuration, + query.into_inner().peer_address, ) .await?; @@ -4499,29 +4486,26 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_list( /// Add prefix to bgp peer allowed export list #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-export/add", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/add", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_export_add( rqctx: RequestContext, - path_params: Path, + path_params: Path, prefix: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsBgpPeerInfoSelector { - configuration, - bgp_peer, - } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + let prefix = prefix.into_inner(); let settings = nexus .switch_port_configuration_bgp_peer_allow_export_add( &opctx, - configuration, - bgp_peer, - prefix.into_inner(), + path_params.into_inner().configuration, + prefix.peer_address, + prefix, ) .await?; Ok(HttpResponseCreated(settings.into())) @@ -4536,29 +4520,26 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_add( /// Remove prefix from bgp peer allowed export list #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/allow-export/remove", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/remove", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_export_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, prefix: TypedBody, ) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsBgpPeerInfoSelector { - configuration, - bgp_peer, - } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + let prefix = prefix.into_inner(); nexus .switch_port_configuration_bgp_peer_allow_export_remove( &opctx, - configuration, - bgp_peer, - prefix.into_inner(), + path_params.into_inner().configuration, + prefix.peer_address, + prefix, ) .await?; Ok(HttpResponseDeleted()) @@ -4573,27 +4554,24 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_remove( /// List communities assigned to a bgp peer #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/community", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_community_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, + query: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsBgpPeerInfoSelector { - configuration, - bgp_peer, - } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus .switch_port_configuration_bgp_peer_community_list( &opctx, - configuration, - bgp_peer, + path_params.into_inner().configuration, + query.into_inner().peer_address, ) .await?; Ok(HttpResponseOk(settings.into_iter().map(Into::into).collect())) @@ -4608,29 +4586,26 @@ async fn networking_switch_port_configuration_bgp_peer_community_list( /// Add community to bgp peer #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/community/add", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/add", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_community_add( rqctx: RequestContext, - path_params: Path, + path_params: Path, community: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsBgpPeerInfoSelector { - configuration, - bgp_peer, - } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + let community = community.into_inner(); let settings = nexus .switch_port_configuration_bgp_peer_community_add( &opctx, - configuration, - bgp_peer, - community.into_inner(), + path_params.into_inner().configuration, + community.peer_address, + community, ) .await?; Ok(HttpResponseCreated(settings.into())) @@ -4645,29 +4620,26 @@ async fn networking_switch_port_configuration_bgp_peer_community_add( /// Remove community from bgp peer #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/{bgp_peer}/community/remove", + path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/remove", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_community_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, community: TypedBody, ) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let params::SwitchPortSettingsBgpPeerInfoSelector { - configuration, - bgp_peer, - } = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + let community = community.into_inner(); nexus .switch_port_configuration_bgp_peer_community_remove( &opctx, - configuration, - bgp_peer, - community.into_inner(), + path_params.into_inner().configuration, + community.peer_address, + community, ) .await?; Ok(HttpResponseDeleted()) diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index 2c77da2a6cf..7e94c71ec9d 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -18,8 +18,8 @@ use nexus_types::external_api::params::{ use nexus_types::external_api::views::Rack; use omicron_common::api::external::ImportExportPolicy; use omicron_common::api::external::{ - self, AddressLotKind, BgpPeer, IdentityMetadataCreateParams, LinkFec, - LinkSpeed, NameOrId, SwitchPort, SwitchPortSettingsView, + self, AddressLotKind, BgpPeerCombined, IdentityMetadataCreateParams, + LinkFec, LinkSpeed, NameOrId, SwitchPort, SwitchPortSettingsView, }; type ControlPlaneTestContext = @@ -298,7 +298,7 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { settings.bgp_peers.insert( "phy0".into(), BgpPeerConfig { - peers: vec![BgpPeer { + peers: vec![BgpPeerCombined { bgp_config: NameOrId::Name("as47".parse().unwrap()), interface_name: "phy0".to_string(), addr: "1.2.3.4".parse().unwrap(), diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index de546a3028f..ee766b7665a 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -198,6 +198,15 @@ networking_switch_port_configuration_address_add POST /v1/system/networking/ networking_switch_port_configuration_address_list GET /v1/system/networking/switch-port-configuration/{configuration}/address networking_switch_port_configuration_address_remove POST /v1/system/networking/switch-port-configuration/{configuration}/address/remove networking_switch_port_configuration_bgp_peer_add POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/add +networking_switch_port_configuration_bgp_peer_allow_export_add POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/add +networking_switch_port_configuration_bgp_peer_allow_export_list GET /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export +networking_switch_port_configuration_bgp_peer_allow_export_remove POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/remove +networking_switch_port_configuration_bgp_peer_allow_import_add POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/add +networking_switch_port_configuration_bgp_peer_allow_import_list GET /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import +networking_switch_port_configuration_bgp_peer_allow_import_remove POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/remove +networking_switch_port_configuration_bgp_peer_community_add POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/add +networking_switch_port_configuration_bgp_peer_community_list GET /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community +networking_switch_port_configuration_bgp_peer_community_remove POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/remove networking_switch_port_configuration_bgp_peer_list GET /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer networking_switch_port_configuration_bgp_peer_remove POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove networking_switch_port_configuration_create POST /v1/system/networking/switch-port-configuration diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index d42d0610734..d578b87b371 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -7,6 +7,9 @@ ping (get "/v1/ping") networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status") networking_switch_port_configuration_address_list (get "/v1/system/networking/switch-port-configuration/{configuration}/address") networking_switch_port_configuration_bgp_peer_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer") +networking_switch_port_configuration_bgp_peer_allow_export_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export") +networking_switch_port_configuration_bgp_peer_allow_import_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import") +networking_switch_port_configuration_bgp_peer_community_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community") networking_switch_port_configuration_geometry_view (get "/v1/system/networking/switch-port-configuration/{configuration}/geometry") networking_switch_port_configuration_link_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link") networking_switch_port_configuration_link_view (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}") @@ -21,6 +24,12 @@ logout (post "/v1/logout") networking_switch_port_configuration_address_add (post "/v1/system/networking/switch-port-configuration/{configuration}/address/add") networking_switch_port_configuration_address_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/address/remove") networking_switch_port_configuration_bgp_peer_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/add") +networking_switch_port_configuration_bgp_peer_allow_export_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/add") +networking_switch_port_configuration_bgp_peer_allow_export_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/remove") +networking_switch_port_configuration_bgp_peer_allow_import_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/add") +networking_switch_port_configuration_bgp_peer_allow_import_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/remove") +networking_switch_port_configuration_bgp_peer_community_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/add") +networking_switch_port_configuration_bgp_peer_community_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/remove") networking_switch_port_configuration_bgp_peer_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove") networking_switch_port_configuration_geometry_set (post "/v1/system/networking/switch-port-configuration/{configuration}/geometry") networking_switch_port_configuration_link_create (post "/v1/system/networking/switch-port-configuration/{configuration}/link") diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index fb8abafdf72..e569f465b42 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -9,8 +9,8 @@ use crate::external_api::shared; use base64::Engine; use chrono::{DateTime, Utc}; use omicron_common::api::external::{ - AddressLotKind, AllowedSourceIps, BfdMode, BgpPeer, ByteCount, Hostname, - IdentityMetadataCreateParams, IdentityMetadataUpdateParams, + AddressLotKind, AllowedSourceIps, BfdMode, BgpPeerCombined, ByteCount, + Hostname, IdentityMetadataCreateParams, IdentityMetadataUpdateParams, InstanceCpuCount, LinkFec, LinkSpeed, Name, NameOrId, PaginationOrder, RouteDestination, RouteTarget, SemverVersion, UserId, }; @@ -1644,6 +1644,9 @@ pub struct RouteAddRemove { /// A prefix allowed to be imported or exported by a bgp peer #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct AllowedPrefixAddRemove { + /// An address identifying the target bgp peer + pub peer_address: IpAddr, + /// The interface the peer is configured on pub interface: Name, @@ -1654,6 +1657,9 @@ pub struct AllowedPrefixAddRemove { /// A community to be added to or removed from a bgp peer #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct BgpCommunityAddRemove { + /// An address identifying the target bgp peer + pub peer_address: IpAddr, + /// The interface the peer is configured on pub interface: Name, @@ -1677,7 +1683,7 @@ pub struct BgpConfigListSelector { #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct BgpPeerConfig { - pub peers: Vec, + pub peers: Vec, } /// Parameters for creating a named set of BGP announcements. @@ -1860,7 +1866,14 @@ pub struct SwitchPortSettingsBgpPeerInfoSelector { pub configuration: NameOrId, /// An address identifying a configured bgp peer. - pub bgp_peer: IpAddr, + pub peer_address: IpAddr, +} + +/// Select a Bgp Peer by address. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BgpPeerQuerySelector { + /// An address identifying a configured bgp peer. + pub peer_address: IpAddr, } /// Select a link settings info object by port settings name and link name. diff --git a/openapi/nexus.json b/openapi/nexus.json index 38f70e87665..19d205ec17e 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7489,6 +7489,429 @@ } } }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "List prefixes allowed to be exported by a given bgp peer", + "operationId": "networking_switch_port_configuration_bgp_peer_allow_export_list", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "peer_address", + "description": "An address identifying a configured bgp peer.", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_BgpAllowedPrefix", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpAllowedPrefix" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/add": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Add prefix to bgp peer allowed export list", + "operationId": "networking_switch_port_configuration_bgp_peer_allow_export_add", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllowedPrefixAddRemove" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpAllowedPrefix" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/remove": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Remove prefix from bgp peer allowed export list", + "operationId": "networking_switch_port_configuration_bgp_peer_allow_export_remove", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllowedPrefixAddRemove" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "List prefixes allowed to be imported by a given bgp peer", + "operationId": "networking_switch_port_configuration_bgp_peer_allow_import_list", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "peer_address", + "description": "An address identifying a configured bgp peer.", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_BgpAllowedPrefix", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpAllowedPrefix" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/add": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Add prefix to bgp peer allowed import list", + "operationId": "networking_switch_port_configuration_bgp_peer_allow_import_add", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllowedPrefixAddRemove" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpAllowedPrefix" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/remove": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Remove prefix from bgp peer allowed import list", + "operationId": "networking_switch_port_configuration_bgp_peer_allow_import_remove", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllowedPrefixAddRemove" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "List communities assigned to a bgp peer", + "operationId": "networking_switch_port_configuration_bgp_peer_community_list", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "peer_address", + "description": "An address identifying a configured bgp peer.", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_BgpCommunity", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpCommunity" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/add": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Add community to bgp peer", + "operationId": "networking_switch_port_configuration_bgp_peer_community_add", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpCommunityAddRemove" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpCommunity" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/remove": { + "post": { + "tags": [ + "system/networking" + ], + "summary": "Remove community from bgp peer", + "operationId": "networking_switch_port_configuration_bgp_peer_community_remove", + "parameters": [ + { + "in": "path", + "name": "configuration", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BgpCommunityAddRemove" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove": { "post": { "tags": [ @@ -10735,6 +11158,38 @@ "allowed_ips" ] }, + "AllowedPrefixAddRemove": { + "description": "A prefix allowed to be imported or exported by a bgp peer", + "type": "object", + "properties": { + "interface": { + "description": "The interface the peer is configured on", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "peer_address": { + "description": "An address identifying the target bgp peer", + "type": "string", + "format": "ip" + }, + "prefix": { + "description": "The allowed prefix to add or remove", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + } + }, + "required": [ + "interface", + "peer_address", + "prefix" + ] + }, "AllowedSourceIps": { "description": "Description of source IPs allowed to reach rack services.", "oneOf": [ @@ -10988,6 +11443,43 @@ "switch" ] }, + "BgpAllowedPrefix": { + "description": "A BGP allowed prefix entry", + "type": "object", + "properties": { + "addr": { + "description": "Peer Address", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "interface_name": { + "description": "Interface peer is reachable on", + "type": "string" + }, + "port_settings_id": { + "description": "Parent switch port configuration", + "type": "string", + "format": "uuid" + }, + "prefix": { + "description": "Allowed Prefix", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + } + }, + "required": [ + "addr", + "interface_name", + "port_settings_id", + "prefix" + ] + }, "BgpAnnounceSet": { "description": "Represents a BGP announce set by id. The id can be used with other API calls to view and manage the announce set.", "type": "object", @@ -11107,6 +11599,71 @@ "network" ] }, + "BgpCommunity": { + "description": "A BGP community", + "type": "object", + "properties": { + "addr": { + "description": "Peer Address", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "community": { + "description": "Community", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "interface_name": { + "description": "Interface peer is reachable on", + "type": "string" + }, + "port_settings_id": { + "description": "Parent switch port configuration", + "type": "string", + "format": "uuid" + } + }, + "required": [ + "addr", + "community", + "interface_name", + "port_settings_id" + ] + }, + "BgpCommunityAddRemove": { + "description": "A community to be added to or removed from a bgp peer", + "type": "object", + "properties": { + "community": { + "description": "The community to add or remove", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "interface": { + "description": "The interface the peer is configured on", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "peer_address": { + "description": "An address identifying the target bgp peer", + "type": "string", + "format": "ip" + } + }, + "required": [ + "community", + "interface", + "peer_address" + ] + }, "BgpConfig": { "description": "A base BGP configuration.", "type": "object", @@ -11276,6 +11833,127 @@ }, "BgpMessageHistory": {}, "BgpPeer": { + "description": "The information required to configure a BGP peer.", + "type": "object", + "properties": { + "addr": { + "description": "The address of th e host to peer with.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "allow_export_list_active": { + "description": "Enable export policies", + "type": "boolean" + }, + "allow_import_list_active": { + "description": "Enable import policies", + "type": "boolean" + }, + "bgp_config": { + "description": "The global BGP configuration used for establishing a session with this peer.", + "allOf": [ + { + "$ref": "#/components/schemas/NameOrId" + } + ] + }, + "connect_retry": { + "description": "How long to to wait between TCP connection retries (seconds).", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "delay_open": { + "description": "How long to delay sending an open request after establishing a TCP session (seconds).", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "enforce_first_as": { + "description": "Enforce that the first AS in paths received from this peer is the peer's AS.", + "type": "boolean" + }, + "hold_time": { + "description": "How long to hold peer connections between keepalives (seconds).", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "idle_hold_time": { + "description": "How long to hold a peer in idle before attempting a new session (seconds).", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "interface_name": { + "description": "The name of interface to peer on. This is relative to the port configuration this BGP peer configuration is a part of. For example this value could be phy0 to refer to a primary physical interface. Or it could be vlan47 to refer to a VLAN interface.", + "type": "string" + }, + "keepalive": { + "description": "How often to send keepalive requests (seconds).", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "local_pref": { + "nullable": true, + "description": "Apply a local preference to routes received from this peer.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "md5_auth_key": { + "nullable": true, + "description": "Use the given key for TCP-MD5 authentication with the peer.", + "type": "string" + }, + "min_ttl": { + "nullable": true, + "description": "Require messages from a peer have a minimum IP time to live field.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "multi_exit_discriminator": { + "nullable": true, + "description": "Apply the provided multi-exit discriminator (MED) updates sent to the peer.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "remote_asn": { + "nullable": true, + "description": "Require that a peer has a specified ASN.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "vlan_id": { + "nullable": true, + "description": "Associate a VLAN ID with a peer.", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "addr", + "allow_export_list_active", + "allow_import_list_active", + "bgp_config", + "connect_retry", + "delay_open", + "enforce_first_as", + "hold_time", + "idle_hold_time", + "interface_name", + "keepalive" + ] + }, + "BgpPeerCombined": { "description": "A BGP peer configuration for an interface. Includes the set of announcements that will be advertised to the peer identified by `addr`. The `bgp_config` parameter is a reference to global BGP parameters. The `interface_name` indicates what interface the peer should be contacted on.", "type": "object", "properties": { @@ -11417,7 +12095,7 @@ "peers": { "type": "array", "items": { - "$ref": "#/components/schemas/BgpPeer" + "$ref": "#/components/schemas/BgpPeerCombined" } } }, @@ -20499,7 +21177,7 @@ "description": "BGP peer settings.", "type": "array", "items": { - "$ref": "#/components/schemas/BgpPeer" + "$ref": "#/components/schemas/BgpPeerCombined" } }, "groups": { From b1cbebe677d33052736996f0faef802a135f9768 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Tue, 10 Sep 2024 06:45:37 +0000 Subject: [PATCH 28/37] WIP unsquash switch port query errors --- .../db-queries/src/db/datastore/switch_port.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index f8b3a61c64a..87133f01c82 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -173,12 +173,25 @@ impl DataStore { port_settings_dsl::switch_port_settings .filter(switch_port_settings::time_deleted.is_null()) - .filter(switch_port_settings::name.eq(name)) + .filter(switch_port_settings::name.eq(name.clone())) .select(switch_port_settings::id) .limit(1) .first_async::(&*pool) .await - .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + .map_err(|e| { + let msg = "failed to lookup switch port settings by name"; + error!(opctx.log, "{msg}"; "error" => ?e); + + match e { + diesel::result::Error::NotFound => { + Error::not_found_by_name( + ResourceType::SwitchPortSettings, + &name, + ) + } + _ => Error::internal_error(msg), + } + }) } pub async fn switch_ports_using_settings( From 822953eb30b1347d48cc4209a4be57cbe6e208e4 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Tue, 15 Oct 2024 17:15:03 +0000 Subject: [PATCH 29/37] WIP: update tests --- .../src/db/datastore/switch_port.rs | 17 +++---- nexus/src/app/switch_port.rs | 2 +- nexus/src/external_api/http_entrypoints.rs | 6 +-- nexus/tests/integration_tests/endpoints.rs | 28 +++++++++-- nexus/tests/integration_tests/unauthorized.rs | 14 ++++++ nexus/tests/output/nexus_tags.txt | 2 +- .../output/uncovered-authz-endpoints.txt | 2 - openapi/nexus.json | 47 ++++++++++--------- 8 files changed, 74 insertions(+), 44 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 87133f01c82..48ea99542b0 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -261,15 +261,10 @@ impl DataStore { pub async fn switch_port_settings_delete( &self, opctx: &OpContext, - params: ¶ms::SwitchPortSettingsSelector, + params: &NameOrId, ) -> DeleteResult { let conn = self.pool_connection_authorized(opctx).await?; - let selector = match ¶ms.configuration { - None => return Err(Error::invalid_request("name or id required")), - Some(name_or_id) => name_or_id, - }; - let err = OptionalError::new(); // TODO https://github.com/oxidecomputer/omicron/issues/2811 @@ -278,7 +273,7 @@ impl DataStore { .transaction(&conn, |conn| { let err = err.clone(); async move { - do_switch_port_settings_delete(&conn, &selector, err).await + do_switch_port_settings_delete(&conn, ¶ms, err).await } }) .await @@ -290,10 +285,10 @@ impl DataStore { } } } else { - let name = match ¶ms.configuration { - Some(name_or_id) => name_or_id.to_string(), - None => String::new(), - }; + let name = match params { + NameOrId::Id(id) => id.to_string(), + NameOrId::Name(name) => name.to_string(), + }; public_error_from_diesel( e, ErrorHandler::Conflict( diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index eae653b9d52..0304e2ba058 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -117,7 +117,7 @@ impl super::Nexus { pub(crate) async fn switch_port_settings_delete( &self, opctx: &OpContext, - params: ¶ms::SwitchPortSettingsSelector, + params: &NameOrId, ) -> DeleteResult { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; self.db_datastore.switch_port_settings_delete(opctx, params).await diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 8a794ebbecc..3c73d23cfff 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -3797,17 +3797,17 @@ async fn networking_switch_port_configuration_create( /// Delete switch port settings #[endpoint { method = DELETE, - path = "/v1/system/networking/switch-port-configuration", + path = "/v1/system/networking/switch-port-configuration/{configuration}", tags = ["system/networking"], }] async fn networking_switch_port_configuration_delete( rqctx: RequestContext, - query_params: Query, + path_params: Path, ) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let selector = query_params.into_inner(); + let selector = path_params.into_inner().configuration; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; nexus.switch_port_settings_delete(&opctx, &selector).await?; Ok(HttpResponseDeleted()) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index fb42577e95e..30a92ad9e93 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -527,7 +527,7 @@ pub static DEMO_LOOPBACK_CREATE: Lazy = }); pub const DEMO_SWITCH_PORT_SETTINGS_URL: &'static str = - "/v1/system/networking/switch-port-configuration?port_settings=portofino"; + "/v1/system/networking/switch-port-configuration"; pub const DEMO_SWITCH_PORT_SETTINGS_INFO_URL: &'static str = "/v1/system/networking/switch-port-configuration/portofino"; pub static DEMO_SWITCH_PORT_SETTINGS_CREATE: Lazy< @@ -539,6 +539,15 @@ pub static DEMO_SWITCH_PORT_SETTINGS_CREATE: Lazy< }) }); +pub const DEMO_SWITCH_PORT_GEOMETRY_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/geometry"; + +pub static DEMO_SWITCH_PORT_GEOMETRY_CREATE: Lazy< + params::SwitchPortConfigCreate, +> = Lazy::new(|| params::SwitchPortConfigCreate { + geometry: params::SwitchPortGeometry::Qsfp28x1, +}); + pub const DEMO_ADDRESS_LOTS_URL: &'static str = "/v1/system/networking/address-lot"; pub const DEMO_ADDRESS_LOT_URL: &'static str = @@ -2304,7 +2313,6 @@ pub static VERIFY_ENDPOINTS: Lazy> = Lazy::new(|| { &*DEMO_SWITCH_PORT_SETTINGS_CREATE).unwrap(), ), AllowedMethod::Get, - AllowedMethod::Delete ], }, @@ -2313,7 +2321,21 @@ pub static VERIFY_ENDPOINTS: Lazy> = Lazy::new(|| { visibility: Visibility::Public, unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![ - AllowedMethod::GetNonexistent + AllowedMethod::Get, + AllowedMethod::Delete, + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_GEOMETRY_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_GEOMETRY_CREATE).unwrap(), + ), + AllowedMethod::Get, ], }, diff --git a/nexus/tests/integration_tests/unauthorized.rs b/nexus/tests/integration_tests/unauthorized.rs index 92d70de2e0d..f285b4569d4 100644 --- a/nexus/tests/integration_tests/unauthorized.rs +++ b/nexus/tests/integration_tests/unauthorized.rs @@ -334,6 +334,20 @@ static SETUP_REQUESTS: Lazy> = Lazy::new(|| { body: serde_json::to_value(&*DEMO_CERTIFICATE_CREATE).unwrap(), id_routes: vec![], }, + // Create a switch port configuration + SetupReq::Post { + url: &DEMO_SWITCH_PORT_SETTINGS_URL, + body: serde_json::to_value(&*DEMO_SWITCH_PORT_SETTINGS_CREATE) + .unwrap(), + id_routes: vec![], + }, + // Create a switch port geometry + SetupReq::Post { + url: &DEMO_SWITCH_PORT_GEOMETRY_URL, + body: serde_json::to_value(&*DEMO_SWITCH_PORT_GEOMETRY_CREATE) + .unwrap(), + id_routes: vec![], + }, ] }); diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index ee766b7665a..41e7b967516 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -210,7 +210,7 @@ networking_switch_port_configuration_bgp_peer_community_remove POST /v1/syst networking_switch_port_configuration_bgp_peer_list GET /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer networking_switch_port_configuration_bgp_peer_remove POST /v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove networking_switch_port_configuration_create POST /v1/system/networking/switch-port-configuration -networking_switch_port_configuration_delete DELETE /v1/system/networking/switch-port-configuration +networking_switch_port_configuration_delete DELETE /v1/system/networking/switch-port-configuration/{configuration} networking_switch_port_configuration_geometry_set POST /v1/system/networking/switch-port-configuration/{configuration}/geometry networking_switch_port_configuration_geometry_view GET /v1/system/networking/switch-port-configuration/{configuration}/geometry networking_switch_port_configuration_link_create POST /v1/system/networking/switch-port-configuration/{configuration}/link diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index d578b87b371..7e030f47ae6 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -10,7 +10,6 @@ networking_switch_port_configuration_bgp_peer_list (get "/v1/system/networkin networking_switch_port_configuration_bgp_peer_allow_export_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export") networking_switch_port_configuration_bgp_peer_allow_import_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import") networking_switch_port_configuration_bgp_peer_community_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community") -networking_switch_port_configuration_geometry_view (get "/v1/system/networking/switch-port-configuration/{configuration}/geometry") networking_switch_port_configuration_link_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link") networking_switch_port_configuration_link_view (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}") networking_switch_port_configuration_route_list (get "/v1/system/networking/switch-port-configuration/{configuration}/route") @@ -31,7 +30,6 @@ networking_switch_port_configuration_bgp_peer_allow_import_remove (post "/v1/s networking_switch_port_configuration_bgp_peer_community_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/add") networking_switch_port_configuration_bgp_peer_community_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/remove") networking_switch_port_configuration_bgp_peer_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove") -networking_switch_port_configuration_geometry_set (post "/v1/system/networking/switch-port-configuration/{configuration}/geometry") networking_switch_port_configuration_link_create (post "/v1/system/networking/switch-port-configuration/{configuration}/link") networking_switch_port_configuration_route_add (post "/v1/system/networking/switch-port-configuration/{configuration}/route/add") networking_switch_port_configuration_route_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/route/remove") diff --git a/openapi/nexus.json b/openapi/nexus.json index 19d205ec17e..e08adf3f1f7 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7200,26 +7200,36 @@ "$ref": "#/components/responses/Error" } } - }, - "delete": { + } + }, + "/v1/system/networking/switch-port-configuration/{configuration}": { + "get": { "tags": [ "system/networking" ], - "summary": "Delete switch port settings", - "operationId": "networking_switch_port_configuration_delete", + "summary": "Get information about a named set of switch-port-settings", + "operationId": "networking_switch_port_configuration_view", "parameters": [ { - "in": "query", + "in": "path", "name": "configuration", - "description": "An optional name or id to use when selecting a switch port configuration.", + "description": "A name or id to use when selecting a switch port configuration.", + "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" } } ], "responses": { - "204": { - "description": "successful deletion" + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettingsView" + } + } + } }, "4XX": { "$ref": "#/components/responses/Error" @@ -7228,15 +7238,13 @@ "$ref": "#/components/responses/Error" } } - } - }, - "/v1/system/networking/switch-port-configuration/{configuration}": { - "get": { + }, + "delete": { "tags": [ "system/networking" ], - "summary": "Get information about a named set of switch-port-settings", - "operationId": "networking_switch_port_configuration_view", + "summary": "Delete switch port settings", + "operationId": "networking_switch_port_configuration_delete", "parameters": [ { "in": "path", @@ -7249,15 +7257,8 @@ } ], "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsView" - } - } - } + "204": { + "description": "successful deletion" }, "4XX": { "$ref": "#/components/responses/Error" From 9ef345614a90b3fb3e4e52a5ff479d249c0ddb43 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Wed, 16 Oct 2024 21:52:00 +0000 Subject: [PATCH 30/37] add integration tests for new endpoints --- nexus/db-queries/src/db/datastore/bgp.rs | 4 +- .../src/db/datastore/switch_port.rs | 49 ++- nexus/src/app/bgp.rs | 2 +- nexus/src/external_api/http_entrypoints.rs | 10 +- nexus/tests/integration_tests/endpoints.rs | 391 +++++++++++++++++- nexus/tests/integration_tests/unauthorized.rs | 81 ++++ nexus/tests/output/nexus_tags.txt | 2 +- .../output/uncovered-authz-endpoints.txt | 22 - nexus/types/src/external_api/params.rs | 2 +- openapi/nexus.json | 12 +- 10 files changed, 530 insertions(+), 45 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/bgp.rs b/nexus/db-queries/src/db/datastore/bgp.rs index fdb96295435..c0c320424fc 100644 --- a/nexus/db-queries/src/db/datastore/bgp.rs +++ b/nexus/db-queries/src/db/datastore/bgp.rs @@ -223,7 +223,7 @@ impl DataStore { pub async fn bgp_config_delete( &self, opctx: &OpContext, - sel: ¶ms::BgpConfigSelector, + sel: &NameOrId, ) -> DeleteResult { use db::schema::bgp_config; use db::schema::bgp_config::dsl as bgp_config_dsl; @@ -237,7 +237,7 @@ impl DataStore { .transaction(&conn, |conn| { let err = err.clone(); async move { - let name_or_id = sel.name_or_id.clone(); + let name_or_id = sel.clone(); let id: Uuid = match name_or_id { NameOrId::Id(id) => bgp_config_dsl::bgp_config diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 48ea99542b0..e2760b86348 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -26,8 +26,8 @@ use crate::transaction_retry::OptionalError; use async_bb8_diesel::{AsyncRunQueryDsl, Connection}; use diesel::CombineDsl; use diesel::{ - ExpressionMethods, JoinOnDsl, NullableExpressionMethods, PgConnection, - QueryDsl, SelectableHelper, + ExpressionMethods, JoinOnDsl, NullableExpressionMethods, OptionalExtension, + PgConnection, QueryDsl, SelectableHelper, }; use diesel_dtrace::DTraceConnection; use ipnetwork::IpNetwork; @@ -1823,8 +1823,7 @@ impl DataStore { self.transaction_retry_wrapper( "switch_port_configuration_bgp_peer_allow_import_add", - ) - .transaction(&conn, |conn| { + ).transaction(&conn, |conn| { let parent_configuration = configuration.clone(); let new_settings = prefix.clone(); let err = err.clone(); @@ -1858,6 +1857,20 @@ impl DataStore { prefix: new_settings.prefix.into(), }; + let found_config = allow_import::table + .filter(allow_import::port_settings_id.eq(allow_import_config.port_settings_id)) + .filter(allow_import::interface_name.eq(allow_import_config.interface_name.clone())) + .filter(allow_import::addr.eq(allow_import_config.addr)) + .filter(allow_import::prefix.eq(allow_import_config.prefix)) + .select(SwitchPortBgpPeerConfigAllowImport::as_select()) + .get_result_async(&conn) + .await + .optional()?; + + if let Some(config) = found_config { + return Ok(config) + } + let config = diesel::insert_into(allow_import::table) .values(allow_import_config) .returning(SwitchPortBgpPeerConfigAllowImport::as_returning()) @@ -2048,6 +2061,20 @@ impl DataStore { prefix: new_settings.prefix.into(), }; + let found_config = allow_export::table + .filter(allow_export::port_settings_id.eq(allow_export_config.port_settings_id)) + .filter(allow_export::interface_name.eq(allow_export_config.interface_name.clone())) + .filter(allow_export::addr.eq(allow_export_config.addr)) + .filter(allow_export::prefix.eq(allow_export_config.prefix)) + .select(SwitchPortBgpPeerConfigAllowExport::as_select()) + .get_result_async(&conn) + .await + .optional()?; + + if let Some(config) = found_config { + return Ok(config) + } + let config = diesel::insert_into(allow_export::table) .values(allow_export_config) .returning(SwitchPortBgpPeerConfigAllowExport::as_returning()) @@ -2238,6 +2265,20 @@ impl DataStore { community: new_settings.community.into(), }; + let found_config = communities::table + .filter(communities::port_settings_id.eq(community_config.port_settings_id)) + .filter(communities::interface_name.eq(community_config.interface_name.clone())) + .filter(communities::addr.eq(community_config.addr)) + .filter(communities::community.eq(community_config.community)) + .select(SwitchPortBgpPeerConfigCommunity::as_select()) + .get_result_async(&conn) + .await + .optional()?; + + if let Some(config) = found_config { + return Ok(config) + } + let config = diesel::insert_into(communities::table) .values(community_config) .returning(SwitchPortBgpPeerConfigCommunity::as_returning()) diff --git a/nexus/src/app/bgp.rs b/nexus/src/app/bgp.rs index 31a0faa6634..1b027b94f58 100644 --- a/nexus/src/app/bgp.rs +++ b/nexus/src/app/bgp.rs @@ -47,7 +47,7 @@ impl super::Nexus { pub async fn bgp_config_delete( &self, opctx: &OpContext, - sel: ¶ms::BgpConfigSelector, + sel: &NameOrId, ) -> DeleteResult { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; let result = self.db_datastore.bgp_config_delete(opctx, sel).await?; diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 3c73d23cfff..c5ebe7bac40 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -4940,17 +4940,17 @@ async fn networking_bgp_imported_routes_ipv4( /// Delete BGP configuration #[endpoint { method = DELETE, - path = "/v1/system/networking/bgp", + path = "/v1/system/networking/bgp/{bgp_config}", tags = ["system/networking"], }] async fn networking_bgp_config_delete( rqctx: RequestContext, - sel: Query, + sel: Path, ) -> Result { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; - let sel = sel.into_inner(); + let sel = sel.into_inner().bgp_config; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; nexus.bgp_config_delete(&opctx, &sel).await?; Ok(HttpResponseUpdatedNoContent {}) @@ -4974,14 +4974,14 @@ async fn networking_bgp_config_delete( async fn networking_bgp_announce_set_update( rqctx: RequestContext, config: TypedBody, -) -> Result, HttpError> { +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; let config = config.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let result = nexus.bgp_update_announce_set(&opctx, &config).await?; - Ok(HttpResponseCreated::(result.0.into())) + Ok(HttpResponseOk::(result.0.into())) }; apictx .context diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 30a92ad9e93..3d2d0d683c4 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -23,12 +23,15 @@ use nexus_types::external_api::shared; use nexus_types::external_api::shared::IpRange; use nexus_types::external_api::shared::Ipv4Range; use nexus_types::external_api::views::SledProvisionPolicy; +use omicron_common::api::external; use omicron_common::api::external::AddressLotKind; use omicron_common::api::external::AllowedSourceIps; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::InstanceCpuCount; +use omicron_common::api::external::LinkFec; +use omicron_common::api::external::LinkSpeed; use omicron_common::api::external::Name; use omicron_common::api::external::NameOrId; use omicron_common::api::external::RouteDestination; @@ -548,6 +551,137 @@ pub static DEMO_SWITCH_PORT_GEOMETRY_CREATE: Lazy< geometry: params::SwitchPortGeometry::Qsfp28x1, }); +pub const DEMO_SWITCH_PORT_LINK_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/link"; + +pub static DEMO_SWITCH_PORT_LINK_CREATE: Lazy = + Lazy::new(|| params::NamedLinkConfigCreate { + name: "my-link".parse().unwrap(), + mtu: 1500, + lldp_config: None, + fec: LinkFec::None, + speed: LinkSpeed::Speed100G, + autoneg: true, + }); + +pub const DEMO_SWITCH_PORT_LINK_INFO_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/link/my-link"; + +pub const DEMO_SWITCH_PORT_ADDRESS_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/address"; + +pub const DEMO_SWITCH_PORT_ADDRESS_ADD_REMOVE: Lazy = + Lazy::new(|| params::AddressAddRemove { + interface: "qsfp0".parse().unwrap(), + address_lot: NameOrId::Name("parkinglot".parse().unwrap()), + address: "203.0.113.11/24".parse().unwrap(), + vlan_id: None, + }); + +pub const DEMO_SWITCH_PORT_ADDRESS_ADD_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/address/add"; + +pub const DEMO_SWITCH_PORT_ADDRESS_REMOVE_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/address/remove"; + +pub const DEMO_SWITCH_PORT_ROUTE_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/route"; + +pub const DEMO_SWITCH_PORT_ROUTE_ADD_REMOVE: Lazy = + Lazy::new(|| params::RouteAddRemove { + interface: "qsfp0".parse().unwrap(), + dst: "0.0.0.0/0".parse().unwrap(), + gw: "203.0.113.1".parse().unwrap(), + vid: None, + local_pref: None, + }); + +pub const DEMO_SWITCH_PORT_ROUTE_ADD_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/route/add"; + +pub const DEMO_SWITCH_PORT_ROUTE_REMOVE_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/route/remove"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_ADD: Lazy = + Lazy::new(|| external::BgpPeer { + bgp_config: NameOrId::Name("as47".parse().unwrap()), + interface_name: "qsfp0".into(), + addr: "203.0.113.12/24".parse().unwrap(), + hold_time: Default::default(), + idle_hold_time: Default::default(), + delay_open: Default::default(), + connect_retry: Default::default(), + keepalive: Default::default(), + remote_asn: None, + min_ttl: None, + md5_auth_key: None, + multi_exit_discriminator: None, + local_pref: None, + enforce_first_as: false, + allow_import_list_active: false, + allow_export_list_active: false, + vlan_id: None, + }); + +pub const DEMO_SWITCH_PORT_BGP_PEER_ADD_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/add"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_REMOVE: Lazy = + Lazy::new(|| external::BgpPeerRemove { + bgp_config: NameOrId::Name("as47".parse().unwrap()), + interface_name: "qsfp0".into(), + addr: "203.0.113.12".parse().unwrap(), + }); + +pub const DEMO_SWITCH_PORT_BGP_PEER_REMOVE_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/remove"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_ALLOWED_PREFIX: Lazy< + params::AllowedPrefixAddRemove, +> = Lazy::new(|| params::AllowedPrefixAddRemove { + peer_address: "203.0.113.12".parse().unwrap(), + interface: "qsfp0".parse().unwrap(), + prefix: "10.0.0.0/24".parse().unwrap(), +}); + +pub const DEMO_SWITCH_PORT_BGP_PEER_ALLOW_IMPORT_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/allow-import?peer_address=203.0.113.12"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_ALLOW_IMPORT_ADD_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/allow-import/add"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_ALLOW_IMPORT_REMOVE_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/allow-import/remove"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_ALLOW_EXPORT_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/allow-export?peer_address=203.0.113.12"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_ALLOW_EXPORT_ADD_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/allow-export/add"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_ALLOW_EXPORT_REMOVE_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/allow-export/remove"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/community?peer_address=203.0.113.12"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_ADD_REMOVE: Lazy< + params::BgpCommunityAddRemove, +> = Lazy::new(|| params::BgpCommunityAddRemove { + peer_address: "203.0.113.12".parse().unwrap(), + interface: "qsfp0".parse().unwrap(), + community: 100, +}); + +pub const DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_ADD_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/community/add"; + +pub const DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_REMOVE_URL: &'static str = + "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/community/remove"; + pub const DEMO_ADDRESS_LOTS_URL: &'static str = "/v1/system/networking/address-lot"; pub const DEMO_ADDRESS_LOT_URL: &'static str = @@ -574,22 +708,27 @@ pub static DEMO_ADDRESS_LOT_BLOCK_CREATE: Lazy< last_address: "203.0.113.20".parse().unwrap(), }); -pub const DEMO_BGP_CONFIG_CREATE_URL: &'static str = - "/v1/system/networking/bgp?name_or_id=as47"; +pub const DEMO_BGP_CONFIG_URL: &'static str = "/v1/system/networking/bgp"; + pub static DEMO_BGP_CONFIG: Lazy = Lazy::new(|| params::BgpConfigCreate { identity: IdentityMetadataCreateParams { name: "as47".parse().unwrap(), description: "BGP config for AS47".into(), }, - bgp_announce_set_id: NameOrId::Name("instances".parse().unwrap()), + bgp_announce_set_id: NameOrId::Name("a-bag-of-addrs".parse().unwrap()), asn: 47, vrf: None, checker: None, shaper: None, }); + +pub const DEMO_BGP_CONFIG_INFO_URL: &'static str = + "/v1/system/networking/bgp/as47"; + pub const DEMO_BGP_ANNOUNCE_SET_URL: &'static str = "/v1/system/networking/bgp-announce-set"; + pub static DEMO_BGP_ANNOUNCE: Lazy = Lazy::new(|| params::BgpAnnounceSetCreate { identity: IdentityMetadataCreateParams { @@ -601,10 +740,13 @@ pub static DEMO_BGP_ANNOUNCE: Lazy = network: "10.0.0.0/16".parse().unwrap(), }], }); + pub const DEMO_BGP_ANNOUNCE_SET_DELETE_URL: &'static str = "/v1/system/networking/bgp-announce-set/a-bag-of-addrs"; + pub const DEMO_BGP_ANNOUNCEMENT_URL: &'static str = "/v1/system/networking/bgp-announce-set/a-bag-of-addrs/announcement"; + pub const DEMO_BGP_STATUS_URL: &'static str = "/v1/system/networking/bgp-status"; pub const DEMO_BGP_EXPORTED_URL: &'static str = @@ -2340,7 +2482,240 @@ pub static VERIFY_ENDPOINTS: Lazy> = Lazy::new(|| { }, VerifyEndpoint { - url: &DEMO_BGP_CONFIG_CREATE_URL, + url: &DEMO_SWITCH_PORT_LINK_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_LINK_CREATE).unwrap(), + ), + AllowedMethod::Get, + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_LINK_INFO_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + AllowedMethod::Delete, + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_ADDRESS_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_ADDRESS_ADD_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_ADDRESS_ADD_REMOVE + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_ADDRESS_REMOVE_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_ADDRESS_ADD_REMOVE + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_ROUTE_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_ROUTE_ADD_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_ROUTE_ADD_REMOVE + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_ROUTE_REMOVE_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_ROUTE_ADD_REMOVE + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_ADD_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_ADD + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_REMOVE_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_REMOVE + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_ALLOW_IMPORT_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_ALLOW_IMPORT_ADD_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_ALLOWED_PREFIX + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_ALLOW_IMPORT_REMOVE_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_ALLOWED_PREFIX + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_ALLOW_EXPORT_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_ALLOW_EXPORT_ADD_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_ALLOWED_PREFIX + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_ALLOW_EXPORT_REMOVE_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_ALLOWED_PREFIX + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_ADD_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_ADD_REMOVE + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_REMOVE_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Post( + serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_ADD_REMOVE + ).unwrap() + ), + ], + }, + + VerifyEndpoint { + url: &DEMO_BGP_CONFIG_URL, visibility: Visibility::Public, unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![ @@ -2348,6 +2723,14 @@ pub static VERIFY_ENDPOINTS: Lazy> = Lazy::new(|| { serde_json::to_value(&*DEMO_BGP_CONFIG).unwrap(), ), AllowedMethod::Get, + ], + }, + + VerifyEndpoint { + url: &DEMO_BGP_CONFIG_INFO_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ AllowedMethod::Delete ], }, diff --git a/nexus/tests/integration_tests/unauthorized.rs b/nexus/tests/integration_tests/unauthorized.rs index f285b4569d4..c8f8262b0b8 100644 --- a/nexus/tests/integration_tests/unauthorized.rs +++ b/nexus/tests/integration_tests/unauthorized.rs @@ -96,6 +96,16 @@ async fn test_unauthorized(cptestctx: &ControlPlaneTestContext) { .unwrap(), id_routes, ), + SetupReq::Put { url, body, id_routes } => ( + url, + NexusRequest::object_put(client, url, Some(body)) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .map_err(|e| panic!("Failed to PUT to URL: {url}, {e}")) + .unwrap(), + id_routes, + ), }; setup_results.insert(url, result.clone()); @@ -169,6 +179,11 @@ enum SetupReq { body: serde_json::Value, id_routes: Vec<&'static str>, }, + Put { + url: &'static str, + body: serde_json::Value, + id_routes: Vec<&'static str>, + }, } pub static HTTP_SERVER: Lazy = @@ -348,6 +363,72 @@ static SETUP_REQUESTS: Lazy> = Lazy::new(|| { .unwrap(), id_routes: vec![], }, + // Create a switch port link + SetupReq::Post { + url: &DEMO_SWITCH_PORT_LINK_URL, + body: serde_json::to_value(&*DEMO_SWITCH_PORT_LINK_CREATE).unwrap(), + id_routes: vec![], + }, + // Create a switch port address + SetupReq::Post { + url: &DEMO_SWITCH_PORT_ADDRESS_ADD_URL, + body: serde_json::to_value(&*DEMO_SWITCH_PORT_ADDRESS_ADD_REMOVE) + .unwrap(), + id_routes: vec![], + }, + // Create a switch port route + SetupReq::Post { + url: &DEMO_SWITCH_PORT_ROUTE_ADD_URL, + body: serde_json::to_value(&*DEMO_SWITCH_PORT_ROUTE_ADD_REMOVE) + .unwrap(), + id_routes: vec![], + }, + // Create a bgp announce set + SetupReq::Put { + url: &DEMO_BGP_ANNOUNCE_SET_URL, + body: serde_json::to_value(&*DEMO_BGP_ANNOUNCE).unwrap(), + id_routes: vec![], + }, + // Create a bgp config + SetupReq::Post { + url: &DEMO_BGP_CONFIG_URL, + body: serde_json::to_value(&*DEMO_BGP_CONFIG).unwrap(), + id_routes: vec![], + }, + // Create a switch port bgp peer + SetupReq::Post { + url: &DEMO_SWITCH_PORT_BGP_PEER_ADD_URL, + body: serde_json::to_value(&*DEMO_SWITCH_PORT_BGP_PEER_ADD) + .unwrap(), + id_routes: vec![], + }, + // Allow a prefix to be exported by a peer + SetupReq::Post { + url: &DEMO_SWITCH_PORT_BGP_PEER_ALLOW_IMPORT_ADD_URL, + body: serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_ALLOWED_PREFIX, + ) + .unwrap(), + id_routes: vec![], + }, + // Allow a prefix to be imported by a peer + SetupReq::Post { + url: &DEMO_SWITCH_PORT_BGP_PEER_ALLOW_EXPORT_ADD_URL, + body: serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_ALLOWED_PREFIX, + ) + .unwrap(), + id_routes: vec![], + }, + // Add a community to a peer + SetupReq::Post { + url: &DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_ADD_URL, + body: serde_json::to_value( + &*DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_ADD_REMOVE, + ) + .unwrap(), + id_routes: vec![], + }, ] }); diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 41e7b967516..b08057f6e22 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -185,7 +185,7 @@ networking_bgp_announce_set_list GET /v1/system/networking/bgp-anno networking_bgp_announce_set_update PUT /v1/system/networking/bgp-announce-set networking_bgp_announcement_list GET /v1/system/networking/bgp-announce-set/{name_or_id}/announcement networking_bgp_config_create POST /v1/system/networking/bgp -networking_bgp_config_delete DELETE /v1/system/networking/bgp +networking_bgp_config_delete DELETE /v1/system/networking/bgp/{bgp_config} networking_bgp_config_list GET /v1/system/networking/bgp networking_bgp_exported GET /v1/system/networking/bgp-exported networking_bgp_imported_routes_ipv4 GET /v1/system/networking/bgp-routes-ipv4 diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index 7e030f47ae6..c5091c5a3bc 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -1,18 +1,9 @@ API endpoints with no coverage in authz tests: probe_delete (delete "/experimental/v1/probes/{probe}") -networking_switch_port_configuration_link_delete (delete "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}") probe_list (get "/experimental/v1/probes") probe_view (get "/experimental/v1/probes/{probe}") ping (get "/v1/ping") networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status") -networking_switch_port_configuration_address_list (get "/v1/system/networking/switch-port-configuration/{configuration}/address") -networking_switch_port_configuration_bgp_peer_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer") -networking_switch_port_configuration_bgp_peer_allow_export_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export") -networking_switch_port_configuration_bgp_peer_allow_import_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import") -networking_switch_port_configuration_bgp_peer_community_list (get "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community") -networking_switch_port_configuration_link_list (get "/v1/system/networking/switch-port-configuration/{configuration}/link") -networking_switch_port_configuration_link_view (get "/v1/system/networking/switch-port-configuration/{configuration}/link/{link}") -networking_switch_port_configuration_route_list (get "/v1/system/networking/switch-port-configuration/{configuration}/route") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") device_access_token (post "/device/token") @@ -20,16 +11,3 @@ probe_create (post "/experimental/v1/probes") login_saml (post "/login/{silo_name}/saml/{provider_name}") login_local (post "/v1/login/{silo_name}/local") logout (post "/v1/logout") -networking_switch_port_configuration_address_add (post "/v1/system/networking/switch-port-configuration/{configuration}/address/add") -networking_switch_port_configuration_address_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/address/remove") -networking_switch_port_configuration_bgp_peer_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/add") -networking_switch_port_configuration_bgp_peer_allow_export_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/add") -networking_switch_port_configuration_bgp_peer_allow_export_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/remove") -networking_switch_port_configuration_bgp_peer_allow_import_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/add") -networking_switch_port_configuration_bgp_peer_allow_import_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/remove") -networking_switch_port_configuration_bgp_peer_community_add (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/add") -networking_switch_port_configuration_bgp_peer_community_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/remove") -networking_switch_port_configuration_bgp_peer_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove") -networking_switch_port_configuration_link_create (post "/v1/system/networking/switch-port-configuration/{configuration}/link") -networking_switch_port_configuration_route_add (post "/v1/system/networking/switch-port-configuration/{configuration}/route/add") -networking_switch_port_configuration_route_remove (post "/v1/system/networking/switch-port-configuration/{configuration}/route/remove") diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index e569f465b42..301d52340d0 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1671,7 +1671,7 @@ pub struct BgpCommunityAddRemove { #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct BgpConfigSelector { /// A name or id to use when selecting BGP config. - pub name_or_id: NameOrId, + pub bgp_config: NameOrId, } /// List BGP configs with an optional name or id. diff --git a/openapi/nexus.json b/openapi/nexus.json index e08adf3f1f7..5613afe93a5 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -6591,7 +6591,9 @@ "$ref": "#/components/responses/Error" } } - }, + } + }, + "/v1/system/networking/bgp/{bgp_config}": { "delete": { "tags": [ "system/networking" @@ -6600,8 +6602,8 @@ "operationId": "networking_bgp_config_delete", "parameters": [ { - "in": "query", - "name": "name_or_id", + "in": "path", + "name": "bgp_config", "description": "A name or id to use when selecting BGP config.", "required": true, "schema": { @@ -6710,8 +6712,8 @@ "required": true }, "responses": { - "201": { - "description": "successful creation", + "200": { + "description": "successful operation", "content": { "application/json": { "schema": { From f65f6f98ca5372b4050be5a4a4bc5c21da2afd0b Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Wed, 16 Oct 2024 23:53:55 +0000 Subject: [PATCH 31/37] change const to static in test fixtures --- nexus/tests/integration_tests/endpoints.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 3d2d0d683c4..85d9850e2ea 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -570,7 +570,7 @@ pub const DEMO_SWITCH_PORT_LINK_INFO_URL: &'static str = pub const DEMO_SWITCH_PORT_ADDRESS_URL: &'static str = "/v1/system/networking/switch-port-configuration/portofino/address"; -pub const DEMO_SWITCH_PORT_ADDRESS_ADD_REMOVE: Lazy = +pub static DEMO_SWITCH_PORT_ADDRESS_ADD_REMOVE: Lazy = Lazy::new(|| params::AddressAddRemove { interface: "qsfp0".parse().unwrap(), address_lot: NameOrId::Name("parkinglot".parse().unwrap()), @@ -587,7 +587,7 @@ pub const DEMO_SWITCH_PORT_ADDRESS_REMOVE_URL: &'static str = pub const DEMO_SWITCH_PORT_ROUTE_URL: &'static str = "/v1/system/networking/switch-port-configuration/portofino/route"; -pub const DEMO_SWITCH_PORT_ROUTE_ADD_REMOVE: Lazy = +pub static DEMO_SWITCH_PORT_ROUTE_ADD_REMOVE: Lazy = Lazy::new(|| params::RouteAddRemove { interface: "qsfp0".parse().unwrap(), dst: "0.0.0.0/0".parse().unwrap(), @@ -605,7 +605,7 @@ pub const DEMO_SWITCH_PORT_ROUTE_REMOVE_URL: &'static str = pub const DEMO_SWITCH_PORT_BGP_PEER_URL: &'static str = "/v1/system/networking/switch-port-configuration/portofino/bgp-peer"; -pub const DEMO_SWITCH_PORT_BGP_PEER_ADD: Lazy = +pub static DEMO_SWITCH_PORT_BGP_PEER_ADD: Lazy = Lazy::new(|| external::BgpPeer { bgp_config: NameOrId::Name("as47".parse().unwrap()), interface_name: "qsfp0".into(), @@ -629,7 +629,7 @@ pub const DEMO_SWITCH_PORT_BGP_PEER_ADD: Lazy = pub const DEMO_SWITCH_PORT_BGP_PEER_ADD_URL: &'static str = "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/add"; -pub const DEMO_SWITCH_PORT_BGP_PEER_REMOVE: Lazy = +pub static DEMO_SWITCH_PORT_BGP_PEER_REMOVE: Lazy = Lazy::new(|| external::BgpPeerRemove { bgp_config: NameOrId::Name("as47".parse().unwrap()), interface_name: "qsfp0".into(), @@ -639,7 +639,7 @@ pub const DEMO_SWITCH_PORT_BGP_PEER_REMOVE: Lazy = pub const DEMO_SWITCH_PORT_BGP_PEER_REMOVE_URL: &'static str = "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/remove"; -pub const DEMO_SWITCH_PORT_BGP_PEER_ALLOWED_PREFIX: Lazy< +pub static DEMO_SWITCH_PORT_BGP_PEER_ALLOWED_PREFIX: Lazy< params::AllowedPrefixAddRemove, > = Lazy::new(|| params::AllowedPrefixAddRemove { peer_address: "203.0.113.12".parse().unwrap(), @@ -668,7 +668,7 @@ pub const DEMO_SWITCH_PORT_BGP_PEER_ALLOW_EXPORT_REMOVE_URL: &'static str = pub const DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_URL: &'static str = "/v1/system/networking/switch-port-configuration/portofino/bgp-peer/community?peer_address=203.0.113.12"; -pub const DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_ADD_REMOVE: Lazy< +pub static DEMO_SWITCH_PORT_BGP_PEER_COMMUNITY_ADD_REMOVE: Lazy< params::BgpCommunityAddRemove, > = Lazy::new(|| params::BgpCommunityAddRemove { peer_address: "203.0.113.12".parse().unwrap(), From 2225b770d727ffdd47acc9fe8fef8a27f4d74192 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 18 Oct 2024 21:39:29 +0000 Subject: [PATCH 32/37] Round 1 of PR review fixes --- common/src/api/external/mod.rs | 2 +- .../src/db/datastore/address_lot.rs | 91 +++++++++++++------ .../src/db/datastore/switch_port.rs | 2 +- nexus/src/external_api/http_entrypoints.rs | 36 ++++---- nexus/types/src/external_api/params.rs | 22 +---- openapi/nexus.json | 2 +- 6 files changed, 83 insertions(+), 72 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 59c0513286c..492919d6876 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2592,7 +2592,7 @@ pub struct BgpPeer { /// could be vlan47 to refer to a VLAN interface. pub interface_name: String, - /// The address of th e host to peer with. + /// The address of the host to peer with. pub addr: oxnet::IpNet, /// How long to hold peer connections between keepalives (seconds). diff --git a/nexus/db-queries/src/db/datastore/address_lot.rs b/nexus/db-queries/src/db/datastore/address_lot.rs index 6769feaa6ab..8ed805e8e56 100644 --- a/nexus/db-queries/src/db/datastore/address_lot.rs +++ b/nexus/db-queries/src/db/datastore/address_lot.rs @@ -204,14 +204,43 @@ impl DataStore { address_lot_id: Uuid, params: params::AddressLotBlockAddRemove, ) -> CreateResult { + use db::schema::address_lot; use db::schema::address_lot_block::dsl; let conn = self.pool_connection_authorized(opctx).await?; + let err = OptionalError::new(); self.transaction_retry_wrapper("address_lot_create") - .transaction(&conn, |conn| async move { - let found_block: Option = - dsl::address_lot_block + .transaction(&conn, |conn| { + let err = err.clone(); + + async move { + + // Verify parent address lot exists + address_lot::table + .filter(address_lot::id.eq(address_lot_id)) + .select(AddressLot::as_select()) + .get_result_async(&conn) + .await + .map_err(|e: diesel::result::Error| { + match e { + diesel::result::Error::NotFound => { + err.bail( + Error::non_resourcetype_not_found( + format!("unable to find address lot with identifier {address_lot_id}") + ) + ) + }, + _ => { + err.bail(Error::internal_error( + "error while looking up address lot for address lot block" + )) + }, + } + })?; + + let found_block: Option = + dsl::address_lot_block .filter(dsl::address_lot_id.eq(address_lot_id)) .filter( dsl::first_address @@ -227,37 +256,39 @@ impl DataStore { .await .ok(); - let new_block = AddressLotBlock::new( - address_lot_id, - IpNetwork::from(params.first_address), - IpNetwork::from(params.last_address), - ); - - let db_block = match found_block { - Some(v) => v, - None => { - diesel::insert_into(dsl::address_lot_block) - .values(new_block) - .returning(AddressLotBlock::as_returning()) - .get_result_async(&conn) - .await? - } - }; + let new_block = AddressLotBlock::new( + address_lot_id, + IpNetwork::from(params.first_address), + IpNetwork::from(params.last_address), + ); + + let db_block = match found_block { + Some(v) => v, + None => { + diesel::insert_into(dsl::address_lot_block) + .values(new_block) + .returning(AddressLotBlock::as_returning()) + .get_result_async(&conn) + .await? + } + }; - Ok(db_block) + Ok(db_block) + } }) .await .map_err(|e| { - public_error_from_diesel( - e, - ErrorHandler::Conflict( - ResourceType::AddressLotBlock, - &format!( - "block covering range {} - {}", - params.first_address, params.last_address - ), - ), - ) + let message = "address_lot_block_create failed"; + match err.take() { + Some(external_error) => { + error!(opctx.log, "{message}"; "error" => ?external_error); + external_error + }, + None => { + error!(opctx.log, "{message}"; "error" => ?e); + Error::internal_error("error while adding address block to address lot") + }, + } }) } diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index e2760b86348..f5cb1974273 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -1535,7 +1535,7 @@ impl DataStore { } })?; - // resolve id of referenced bgp configuratino + // resolve id of referenced bgp configuration let bgp_config_id = match new_settings.bgp_config { NameOrId::Id(id) => { diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index c5ebe7bac40..729463882ec 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -4067,7 +4067,7 @@ async fn networking_switch_port_configuration_link_delete( /// List addresses assigned to a provided interface configuration #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/address", + path = "/v1/system/networking/switch-port-configuration/{configuration}/address", tags = ["system/networking"], }] async fn networking_switch_port_configuration_address_list( @@ -4095,7 +4095,7 @@ async fn networking_switch_port_configuration_address_list( /// Add address to an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/address/add", + path = "/v1/system/networking/switch-port-configuration/{configuration}/address/add", tags = ["system/networking"], }] async fn networking_switch_port_configuration_address_add( @@ -4129,7 +4129,7 @@ async fn networking_switch_port_configuration_address_add( /// Remove address from an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/address/remove", + path = "/v1/system/networking/switch-port-configuration/{configuration}/address/remove", tags = ["system/networking"], }] async fn networking_switch_port_configuration_address_remove( @@ -4163,7 +4163,7 @@ async fn networking_switch_port_configuration_address_remove( /// List routes assigned to a provided interface configuration #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/route", + path = "/v1/system/networking/switch-port-configuration/{configuration}/route", tags = ["system/networking"], }] async fn networking_switch_port_configuration_route_list( @@ -4191,7 +4191,7 @@ async fn networking_switch_port_configuration_route_list( /// Add route to an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/route/add", + path = "/v1/system/networking/switch-port-configuration/{configuration}/route/add", tags = ["system/networking"], }] async fn networking_switch_port_configuration_route_add( @@ -4221,7 +4221,7 @@ async fn networking_switch_port_configuration_route_add( /// Remove route from an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/route/remove", + path = "/v1/system/networking/switch-port-configuration/{configuration}/route/remove", tags = ["system/networking"], }] async fn networking_switch_port_configuration_route_remove( @@ -4255,7 +4255,7 @@ async fn networking_switch_port_configuration_route_remove( /// List bgp peers assigned to a provided interface configuration #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_list( @@ -4284,7 +4284,7 @@ async fn networking_switch_port_configuration_bgp_peer_list( /// Add bgp peer to an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/add", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/add", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_add( @@ -4318,7 +4318,7 @@ async fn networking_switch_port_configuration_bgp_peer_add( /// Remove bgp peer from an interface configuration #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/remove", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_remove( @@ -4352,7 +4352,7 @@ async fn networking_switch_port_configuration_bgp_peer_remove( /// List prefixes allowed to be imported by a given bgp peer #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_import_list( @@ -4385,7 +4385,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_list( /// Add prefix to bgp peer allowed import list #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/add", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/add", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_import_add( @@ -4419,7 +4419,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_add( /// Remove prefix from bgp peer allowed import list #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/remove", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-import/remove", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_import_remove( @@ -4453,7 +4453,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_import_remove( /// List prefixes allowed to be exported by a given bgp peer #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_export_list( @@ -4486,7 +4486,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_list( /// Add prefix to bgp peer allowed export list #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/add", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/add", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_export_add( @@ -4520,7 +4520,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_add( /// Remove prefix from bgp peer allowed export list #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/remove", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/allow-export/remove", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_allow_export_remove( @@ -4554,7 +4554,7 @@ async fn networking_switch_port_configuration_bgp_peer_allow_export_remove( /// List communities assigned to a bgp peer #[endpoint { method = GET, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_community_list( @@ -4586,7 +4586,7 @@ async fn networking_switch_port_configuration_bgp_peer_community_list( /// Add community to bgp peer #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/add", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/add", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_community_add( @@ -4620,7 +4620,7 @@ async fn networking_switch_port_configuration_bgp_peer_community_add( /// Remove community from bgp peer #[endpoint { method = POST, - path ="/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/remove", + path = "/v1/system/networking/switch-port-configuration/{configuration}/bgp-peer/community/remove", tags = ["system/networking"], }] async fn networking_switch_port_configuration_bgp_peer_community_remove( diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 301d52340d0..5d7b7a1a80c 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1859,17 +1859,7 @@ pub struct SwitchPortSettingsInfoSelector { pub configuration: NameOrId, } -/// Select a port settings info object by name or id and peer by address. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SwitchPortSettingsBgpPeerInfoSelector { - /// A name or id to use when selecting a switch port configuration. - pub configuration: NameOrId, - - /// An address identifying a configured bgp peer. - pub peer_address: IpAddr, -} - -/// Select a Bgp Peer by address. +/// Select a bgp peer by address. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct BgpPeerQuerySelector { /// An address identifying a configured bgp peer. @@ -1886,16 +1876,6 @@ pub struct SwitchPortSettingsLinkInfoSelector { pub link: Name, } -/// Select an interface settings info object by port settings name / id and interface name. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SwitchPortSettingsInterfaceInfoSelector { - /// A name or id to use when selecting a switch port configuration. - pub configuration: NameOrId, - - /// Interface name - pub interface: Name, -} - /// Select a switch port by name. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct SwitchPortPathSelector { diff --git a/openapi/nexus.json b/openapi/nexus.json index 5613afe93a5..4a131950efd 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -11840,7 +11840,7 @@ "type": "object", "properties": { "addr": { - "description": "The address of th e host to peer with.", + "description": "The address of the host to peer with.", "allOf": [ { "$ref": "#/components/schemas/IpNet" From 155e5d431f7707d69de500a94c112ffebb34577b Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Sat, 19 Oct 2024 03:29:35 +0000 Subject: [PATCH 33/37] Round 2 of PR review fixes --- common/src/api/external/mod.rs | 34 +++++++------- .../src/db/datastore/switch_port.rs | 10 ++-- nexus/src/app/switch_port.rs | 2 +- nexus/src/external_api/http_entrypoints.rs | 2 +- nexus/types/src/external_api/params.rs | 13 +++--- openapi/nexus.json | 46 +++++++++---------- 6 files changed, 53 insertions(+), 54 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 492919d6876..feafbe7d7be 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2522,7 +2522,7 @@ pub struct BgpPeerCombined { /// peer. pub bgp_config: NameOrId, - /// The name of interface to peer on. This is relative to the port + /// The name of the interface to peer on. This is relative to the port /// configuration this BGP peer configuration is a part of. For example this /// value could be phy0 to refer to a primary physical interface. Or it /// could be vlan47 to refer to a VLAN interface. @@ -2548,19 +2548,19 @@ pub struct BgpPeerCombined { /// How often to send keepalive requests (seconds). pub keepalive: u32, - /// Require that a peer has a specified ASN. + /// Require that this peer has a specified ASN. pub remote_asn: Option, - /// Require messages from a peer have a minimum IP time to live field. + /// Require messages from this peer have a minimum IP time to live field. pub min_ttl: Option, - /// Use the given key for TCP-MD5 authentication with the peer. + /// Use the given key for TCP-MD5 authentication with this peer. pub md5_auth_key: Option, - /// Apply the provided multi-exit discriminator (MED) updates sent to the peer. + /// Apply a multi-exit discriminator (MED) in updates sent to this peer. pub multi_exit_discriminator: Option, - /// Include the provided communities in updates sent to the peer. + /// Include the provided communities in updates sent to this peer. pub communities: Vec, /// Apply a local preference to routes received from this peer. @@ -2569,13 +2569,13 @@ pub struct BgpPeerCombined { /// Enforce that the first AS in paths received from this peer is the peer's AS. pub enforce_first_as: bool, - /// Define import policy for a peer. + /// Define import policy for this peer. pub allowed_import: ImportExportPolicy, - /// Define export policy for a peer. + /// Define export policy for this peer. pub allowed_export: ImportExportPolicy, - /// Associate a VLAN ID with a peer. + /// Associate a VLAN ID with this peer. pub vlan_id: Option, } @@ -2586,7 +2586,7 @@ pub struct BgpPeer { /// peer. pub bgp_config: NameOrId, - /// The name of interface to peer on. This is relative to the port + /// The name of the interface to peer on. This is relative to the port /// configuration this BGP peer configuration is a part of. For example this /// value could be phy0 to refer to a primary physical interface. Or it /// could be vlan47 to refer to a VLAN interface. @@ -2598,7 +2598,7 @@ pub struct BgpPeer { /// How long to hold peer connections between keepalives (seconds). pub hold_time: u32, - /// How long to hold a peer in idle before attempting a new session + /// How long to hold this peer in idle before attempting a new session /// (seconds). pub idle_hold_time: u32, @@ -2612,16 +2612,16 @@ pub struct BgpPeer { /// How often to send keepalive requests (seconds). pub keepalive: u32, - /// Require that a peer has a specified ASN. + /// Require that this peer have a specified ASN. pub remote_asn: Option, - /// Require messages from a peer have a minimum IP time to live field. + /// Require messages from this peer to have a minimum IP time to live field. pub min_ttl: Option, - /// Use the given key for TCP-MD5 authentication with the peer. + /// Use the given key for TCP-MD5 authentication with this peer. pub md5_auth_key: Option, - /// Apply the provided multi-exit discriminator (MED) updates sent to the peer. + /// Apply a multi-exit discriminator (MED) in updates sent to this peer. pub multi_exit_discriminator: Option, /// Apply a local preference to routes received from this peer. @@ -2636,7 +2636,7 @@ pub struct BgpPeer { /// Enable export policies pub allow_export_list_active: bool, - /// Associate a VLAN ID with a peer. + /// Associate a VLAN ID with this peer. pub vlan_id: Option, } @@ -2647,7 +2647,7 @@ pub struct BgpPeerRemove { /// peer. pub bgp_config: NameOrId, - /// The name of interface to peer on. This is relative to the port + /// The name of the interface to peer on. This is relative to the port /// configuration this BGP peer configuration is a part of. For example this /// value could be phy0 to refer to a primary physical interface. Or it /// could be vlan47 to refer to a VLAN interface. diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index f5cb1974273..578b363b427 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -1060,8 +1060,8 @@ impl DataStore { .map_err(|e| match e { ReserveBlockTxnError::CustomError(e) => { let message = match e { - ReserveBlockError::AddressUnavailable => "address is unavailable", - ReserveBlockError::AddressNotInLot => "address is not in lot", + ReserveBlockError::AddressUnavailable => "address unavailable", + ReserveBlockError::AddressNotInLot => "address not in lot", }; err.bail(Error::conflict(message)) } @@ -1890,7 +1890,7 @@ impl DataStore { }, None => { error!(opctx.log, "{message}"; "error" => ?e); - Error::internal_error("error while adding prefix to allowed import list") + Error::internal_error("error while adding entry to allowed import list") }, } }) @@ -2094,7 +2094,7 @@ impl DataStore { }, None => { error!(opctx.log, "{message}"; "error" => ?e); - Error::internal_error("error while adding prefix to allowed export list") + Error::internal_error("error while adding entry to allowed export list") }, } }) @@ -2210,7 +2210,7 @@ impl DataStore { .load_async(&*self.pool_connection_authorized(opctx).await?) .await .map_err(|e: diesel::result::Error| { - let msg = "error while looking up bgp peer allowed export list"; + let msg = "error while looking up bgp peer community list"; error!(opctx.log, "{msg}"; "error" => ?e); Error::internal_error(msg) }) diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 0304e2ba058..7fe3e091e66 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -181,7 +181,7 @@ impl super::Nexus { name_or_id: NameOrId, new_settings: params::NamedLinkConfigCreate, ) -> CreateResult { - opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; self.db_datastore .switch_port_configuration_link_create( opctx, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 729463882ec..fbf7b59e94b 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -3588,7 +3588,7 @@ async fn networking_address_lot_block_remove( nexus.address_lot_lookup(&opctx, path.address_lot)?; let (.., authz_address_lot) = - address_lot_lookup.lookup_for(authz::Action::CreateChild).await?; + address_lot_lookup.lookup_for(authz::Action::Delete).await?; nexus .address_lot_block_delete( diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 5d7b7a1a80c..ca20792bb84 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1615,13 +1615,12 @@ pub struct Route { /// VLAN id the gateway is reachable over. pub vid: Option, - /// Local preference for route. Higher preference indictes precedence + /// Local preference for route. Higher preference indicates precedence /// within and across protocols. pub local_pref: Option, } -/// A route to a destination network through a gateway address to add or -/// remove to an interface. +/// A network route to to add to or remove from an interface. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct RouteAddRemove { /// The interface to configure the route on @@ -1636,7 +1635,7 @@ pub struct RouteAddRemove { /// VLAN id the gateway is reachable over. pub vid: Option, - /// Local preference for route. Higher preference indictes precedence + /// Local preference for route. Higher preference indicates precedence /// within and across protocols. pub local_pref: Option, } @@ -1822,14 +1821,14 @@ pub struct Address { /// The address lot this address is drawn from. pub address_lot: NameOrId, - /// The address and prefix length of this address. + /// The address and subnet mask pub address: IpNet, /// Optional VLAN ID for this address pub vlan_id: Option, } -/// An address to be added or removed from an interface +/// An address to be added to or removed from an interface #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct AddressAddRemove { /// The name of the interface @@ -1838,7 +1837,7 @@ pub struct AddressAddRemove { /// The address lot this address is drawn from. pub address_lot: NameOrId, - /// The address and prefix length of this address. + /// The address and subnet mask pub address: IpNet, /// Optional VLAN ID for this address diff --git a/openapi/nexus.json b/openapi/nexus.json index 4a131950efd..f90f16b705a 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -10815,7 +10815,7 @@ "type": "object", "properties": { "address": { - "description": "The address and prefix length of this address.", + "description": "The address and subnet mask", "allOf": [ { "$ref": "#/components/schemas/IpNet" @@ -10844,11 +10844,11 @@ ] }, "AddressAddRemove": { - "description": "An address to be added or removed from an interface", + "description": "An address to be added to or removed from an interface", "type": "object", "properties": { "address": { - "description": "The address and prefix length of this address.", + "description": "The address and subnet mask", "allOf": [ { "$ref": "#/components/schemas/IpNet" @@ -11886,13 +11886,13 @@ "minimum": 0 }, "idle_hold_time": { - "description": "How long to hold a peer in idle before attempting a new session (seconds).", + "description": "How long to hold this peer in idle before attempting a new session (seconds).", "type": "integer", "format": "uint32", "minimum": 0 }, "interface_name": { - "description": "The name of interface to peer on. This is relative to the port configuration this BGP peer configuration is a part of. For example this value could be phy0 to refer to a primary physical interface. Or it could be vlan47 to refer to a VLAN interface.", + "description": "The name of the interface to peer on. This is relative to the port configuration this BGP peer configuration is a part of. For example this value could be phy0 to refer to a primary physical interface. Or it could be vlan47 to refer to a VLAN interface.", "type": "string" }, "keepalive": { @@ -11910,33 +11910,33 @@ }, "md5_auth_key": { "nullable": true, - "description": "Use the given key for TCP-MD5 authentication with the peer.", + "description": "Use the given key for TCP-MD5 authentication with this peer.", "type": "string" }, "min_ttl": { "nullable": true, - "description": "Require messages from a peer have a minimum IP time to live field.", + "description": "Require messages from this peer to have a minimum IP time to live field.", "type": "integer", "format": "uint8", "minimum": 0 }, "multi_exit_discriminator": { "nullable": true, - "description": "Apply the provided multi-exit discriminator (MED) updates sent to the peer.", + "description": "Apply a multi-exit discriminator (MED) in updates sent to this peer.", "type": "integer", "format": "uint32", "minimum": 0 }, "remote_asn": { "nullable": true, - "description": "Require that a peer has a specified ASN.", + "description": "Require that this peer have a specified ASN.", "type": "integer", "format": "uint32", "minimum": 0 }, "vlan_id": { "nullable": true, - "description": "Associate a VLAN ID with a peer.", + "description": "Associate a VLAN ID with this peer.", "type": "integer", "format": "uint16", "minimum": 0 @@ -11966,7 +11966,7 @@ "format": "ip" }, "allowed_export": { - "description": "Define export policy for a peer.", + "description": "Define export policy for this peer.", "allOf": [ { "$ref": "#/components/schemas/ImportExportPolicy" @@ -11974,7 +11974,7 @@ ] }, "allowed_import": { - "description": "Define import policy for a peer.", + "description": "Define import policy for this peer.", "allOf": [ { "$ref": "#/components/schemas/ImportExportPolicy" @@ -11990,7 +11990,7 @@ ] }, "communities": { - "description": "Include the provided communities in updates sent to the peer.", + "description": "Include the provided communities in updates sent to this peer.", "type": "array", "items": { "type": "integer", @@ -12027,7 +12027,7 @@ "minimum": 0 }, "interface_name": { - "description": "The name of interface to peer on. This is relative to the port configuration this BGP peer configuration is a part of. For example this value could be phy0 to refer to a primary physical interface. Or it could be vlan47 to refer to a VLAN interface.", + "description": "The name of the interface to peer on. This is relative to the port configuration this BGP peer configuration is a part of. For example this value could be phy0 to refer to a primary physical interface. Or it could be vlan47 to refer to a VLAN interface.", "type": "string" }, "keepalive": { @@ -12045,33 +12045,33 @@ }, "md5_auth_key": { "nullable": true, - "description": "Use the given key for TCP-MD5 authentication with the peer.", + "description": "Use the given key for TCP-MD5 authentication with this peer.", "type": "string" }, "min_ttl": { "nullable": true, - "description": "Require messages from a peer have a minimum IP time to live field.", + "description": "Require messages from this peer have a minimum IP time to live field.", "type": "integer", "format": "uint8", "minimum": 0 }, "multi_exit_discriminator": { "nullable": true, - "description": "Apply the provided multi-exit discriminator (MED) updates sent to the peer.", + "description": "Apply a multi-exit discriminator (MED) in updates sent to this peer.", "type": "integer", "format": "uint32", "minimum": 0 }, "remote_asn": { "nullable": true, - "description": "Require that a peer has a specified ASN.", + "description": "Require that this peer has a specified ASN.", "type": "integer", "format": "uint32", "minimum": 0 }, "vlan_id": { "nullable": true, - "description": "Associate a VLAN ID with a peer.", + "description": "Associate a VLAN ID with this peer.", "type": "integer", "format": "uint16", "minimum": 0 @@ -12124,7 +12124,7 @@ ] }, "interface_name": { - "description": "The name of interface to peer on. This is relative to the port configuration this BGP peer configuration is a part of. For example this value could be phy0 to refer to a primary physical interface. Or it could be vlan47 to refer to a VLAN interface.", + "description": "The name of the interface to peer on. This is relative to the port configuration this BGP peer configuration is a part of. For example this value could be phy0 to refer to a primary physical interface. Or it could be vlan47 to refer to a VLAN interface.", "type": "string" } }, @@ -18881,7 +18881,7 @@ }, "local_pref": { "nullable": true, - "description": "Local preference for route. Higher preference indictes precedence within and across protocols.", + "description": "Local preference for route. Higher preference indicates precedence within and across protocols.", "type": "integer", "format": "uint32", "minimum": 0 @@ -18900,7 +18900,7 @@ ] }, "RouteAddRemove": { - "description": "A route to a destination network through a gateway address to add or remove to an interface.", + "description": "A network route to to add to or remove from an interface.", "type": "object", "properties": { "dst": { @@ -18926,7 +18926,7 @@ }, "local_pref": { "nullable": true, - "description": "Local preference for route. Higher preference indictes precedence within and across protocols.", + "description": "Local preference for route. Higher preference indicates precedence within and across protocols.", "type": "integer", "format": "uint32", "minimum": 0 From 36d700aaf14caf3007ec5096b26992d256164ebe Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Tue, 22 Oct 2024 22:21:46 +0000 Subject: [PATCH 34/37] Endpoints for managing active switch port configs * migrate settings apply / clear to configuration set / clear * add endpoint for viewing which configuration is active --- nexus/db-model/src/schema.rs | 13 +- .../src/db/datastore/switch_port.rs | 36 +++- nexus/src/app/rack.rs | 4 +- nexus/src/app/switch_port.rs | 42 +++-- nexus/src/external_api/http_entrypoints.rs | 135 +++++++++++--- nexus/tests/output/nexus_tags.txt | 3 + .../output/uncovered-authz-endpoints.txt | 3 + nexus/types/src/external_api/params.rs | 17 +- openapi/nexus.json | 175 +++++++++++++++++- 9 files changed, 366 insertions(+), 62 deletions(-) diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index c50609399c5..13e9ca5ef8c 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -1882,17 +1882,6 @@ allow_tables_to_appear_in_same_query!(external_ip, instance); allow_tables_to_appear_in_same_query!(external_ip, project); allow_tables_to_appear_in_same_query!(external_ip, ip_pool_resource); -allow_tables_to_appear_in_same_query!( - switch_port, - switch_port_settings_route_config -); - -allow_tables_to_appear_in_same_query!( - switch_port, - switch_port_settings_bgp_peer_config, - bgp_config -); - allow_tables_to_appear_in_same_query!(disk, virtual_provisioning_resource); allow_tables_to_appear_in_same_query!(volume, virtual_provisioning_resource); @@ -1906,6 +1895,7 @@ allow_tables_to_appear_in_same_query!(sled, sled_instance); joinable!(network_interface -> probe (parent_id)); allow_tables_to_appear_in_same_query!( + switch_port, switch_port_settings, switch_port_settings_port_config, switch_port_settings_link_config, @@ -1915,4 +1905,5 @@ allow_tables_to_appear_in_same_query!( switch_port_settings_bgp_peer_config_allow_export, switch_port_settings_bgp_peer_config_allow_import, switch_port_settings_bgp_peer_config_communities, + bgp_config, ); diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 578b363b427..3ab7abe139c 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -43,7 +43,7 @@ use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ self, BgpPeer, BgpPeerRemove, CreateResult, DataPageParams, DeleteResult, Error, ImportExportPolicy, ListResultVec, LookupResult, NameOrId, - ResourceType, UpdateResult, + ResourceType, SwitchLocation, UpdateResult, }; use ref_cast::RefCast; use serde::{Deserialize, Serialize}; @@ -2740,11 +2740,43 @@ impl DataStore { Ok(()) } + pub async fn switch_port_get_active_configuration( + &self, + opctx: &OpContext, + rack_id: Uuid, + switch_location: SwitchLocation, + port_name: Name, + ) -> LookupResult> { + use db::schema::switch_port; + use db::schema::switch_port_settings; + + let conn = self.pool_connection_authorized(opctx).await?; + + let active_configuration = switch_port::table + .inner_join( + switch_port_settings::table.on(switch_port_settings::id + .nullable() + .eq(switch_port::port_settings_id)), + ) + .filter(switch_port::rack_id.eq(rack_id)) + .filter( + switch_port::switch_location.eq(switch_location.to_string()), + ) + .filter(switch_port::port_name.eq(port_name.to_string())) + .select(SwitchPortSettings::as_select()) + .get_result_async::(&*conn) + .await + .optional() + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(active_configuration) + } + pub async fn switch_port_get_id( &self, opctx: &OpContext, rack_id: Uuid, - switch_location: Name, + switch_location: SwitchLocation, port_name: Name, ) -> LookupResult { use db::schema::switch_port; diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index 52c2351641f..d59c8000a51 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -591,7 +591,7 @@ impl super::Nexus { for (idx, uplink_config) in rack_network_config.ports.iter().enumerate() { let switch = uplink_config.switch.to_string(); - let switch_location = Name::from_str(&switch).map_err(|e| { + let switch_location = switch.parse().map_err(|e| { Error::internal_error(&format!( "unable to use {switch} as Name: {e}" )) @@ -730,7 +730,7 @@ impl super::Nexus { .switch_port_get_id( opctx, rack_id, - switch_location.into(), + switch_location, Name::from_str(&uplink_config.port).unwrap().into(), ) .await?; diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 7fe3e091e66..01c87e61725 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -540,11 +540,30 @@ impl super::Nexus { .await } + pub(crate) async fn switch_port_view_configuration( + self: &Arc, + opctx: &OpContext, + port: &Name, + rack_id: Uuid, + switch_location: SwitchLocation, + ) -> LookupResult> { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + self.db_datastore + .switch_port_get_active_configuration( + opctx, + rack_id, + switch_location, + port.clone().into(), + ) + .await + } + pub(crate) async fn switch_port_apply_settings( self: &Arc, opctx: &OpContext, port: &Name, - selector: ¶ms::SwitchPortSelector, + rack_id: Uuid, + switch_location: SwitchLocation, settings: ¶ms::SwitchPortApplySettings, ) -> UpdateResult<()> { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; @@ -552,8 +571,8 @@ impl super::Nexus { .db_datastore .switch_port_get_id( opctx, - selector.rack_id, - selector.switch_location.clone().into(), + rack_id, + switch_location, port.clone().into(), ) .await?; @@ -586,15 +605,16 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, port: &Name, - params: ¶ms::SwitchPortSelector, + rack_id: Uuid, + switch_location: SwitchLocation, ) -> UpdateResult<()> { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; let switch_port_id = self .db_datastore .switch_port_get_id( opctx, - params.rack_id, - params.switch_location.clone().into(), + rack_id, + switch_location, port.clone().into(), ) .await?; @@ -644,17 +664,11 @@ impl super::Nexus { pub(crate) async fn switch_port_status( &self, opctx: &OpContext, - switch: Name, + switch: SwitchLocation, port: Name, ) -> Result { opctx.authorize(authz::Action::Read, &authz::FLEET).await?; - let loc: SwitchLocation = switch.as_str().parse().map_err(|e| { - Error::invalid_request(&format!( - "invalid switch name {switch}: {e}" - )) - })?; - let port_id = PortId::Qsfp(port.as_str().parse().map_err(|e| { Error::invalid_request(&format!("invalid port name: {port} {e}")) })?); @@ -666,7 +680,7 @@ impl super::Nexus { Error::internal_error(&format!("dpd clients get: {e}")) })?; - let dpd = dpd_clients.get(&loc).ok_or(Error::internal_error( + let dpd = dpd_clients.get(&switch).ok_or(Error::internal_error( &format!("no client for switch {switch}"), ))?; diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index fbf7b59e94b..f4547772e03 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -276,50 +276,32 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register(networking_switch_port_configuration_create)?; api.register(networking_switch_port_configuration_delete)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_geometry_view)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_geometry_set)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_create)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_delete)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_view)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_link_list)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_address_add)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_address_remove)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_address_list)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_route_add)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_route_remove)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_route_list)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_bgp_peer_add)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_bgp_peer_remove)?; - // TODO: Levon - add tests api.register(networking_switch_port_configuration_bgp_peer_list)?; - // TODO: Levon - add tests api.register( networking_switch_port_configuration_bgp_peer_allow_import_add, )?; - // TODO: Levon - add tests api.register( networking_switch_port_configuration_bgp_peer_allow_import_remove, )?; - // TODO: Levon - add tests api.register( networking_switch_port_configuration_bgp_peer_allow_import_list, )?; @@ -327,11 +309,9 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register( networking_switch_port_configuration_bgp_peer_allow_export_add, )?; - // TODO: Levon - add tests api.register( networking_switch_port_configuration_bgp_peer_allow_export_remove, )?; - // TODO: Levon - add tests api.register( networking_switch_port_configuration_bgp_peer_allow_export_list, )?; @@ -339,11 +319,9 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register( networking_switch_port_configuration_bgp_peer_community_add, )?; - // TODO: Levon - add tests api.register( networking_switch_port_configuration_bgp_peer_community_remove, )?; - // TODO: Levon - add tests api.register( networking_switch_port_configuration_bgp_peer_community_list, )?; @@ -353,6 +331,11 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register(networking_switch_port_apply_settings)?; api.register(networking_switch_port_clear_settings)?; + // TODO: Levon - add tests + api.register(networking_switch_port_active_configuration_view)?; + api.register(networking_switch_port_active_configuration_set)?; + api.register(networking_switch_port_active_configuration_clear)?; + api.register(networking_bgp_config_create)?; api.register(networking_bgp_config_list)?; api.register(networking_bgp_status)?; @@ -4717,11 +4700,100 @@ async fn networking_switch_port_status( .await } +/// View switch port configuration +#[endpoint { + method = GET, + path = "/v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration", + tags = ["system/hardware"], +}] +async fn networking_switch_port_active_configuration_view( + rqctx: RequestContext, + path_params: Path, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortConfigurationSelector { rack_id, switch, port } = + path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + let configuration = nexus + .switch_port_view_configuration(&opctx, &port, rack_id, switch) + .await?; + Ok(HttpResponseOk(configuration.map(Into::into))) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Set switch port configuration +#[endpoint { + method = PUT, + path = "/v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration", + tags = ["system/hardware"], +}] +async fn networking_switch_port_active_configuration_set( + rqctx: RequestContext, + path_params: Path, + settings_body: TypedBody, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortConfigurationSelector { rack_id, switch, port } = + path_params.into_inner(); + let settings = settings_body.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + nexus + .switch_port_apply_settings( + &opctx, &port, rack_id, switch, &settings, + ) + .await?; + Ok(HttpResponseUpdatedNoContent {}) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + +/// Clear switch port configuration +#[endpoint { + method = DELETE, + path = "/v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration", + tags = ["system/hardware"], +}] +async fn networking_switch_port_active_configuration_clear( + rqctx: RequestContext, + path_params: Path, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let params::SwitchPortConfigurationSelector { rack_id, switch, port } = + path_params.into_inner(); + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + nexus + .switch_port_clear_settings(&opctx, &port, rack_id, switch) + .await?; + Ok(HttpResponseDeleted {}) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await +} + /// Apply switch port settings #[endpoint { method = POST, path = "/v1/system/hardware/switch-port/{port}/settings", tags = ["system/hardware"], + deprecated = true, }] async fn networking_switch_port_apply_settings( rqctx: RequestContext, @@ -4733,11 +4805,18 @@ async fn networking_switch_port_apply_settings( let handler = async { let nexus = &apictx.context.nexus; let port = path_params.into_inner().port; - let query = query_params.into_inner(); + let params::SwitchPortSelector { rack_id, switch_location } = + query_params.into_inner(); let settings = settings_body.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; nexus - .switch_port_apply_settings(&opctx, &port, &query, &settings) + .switch_port_apply_settings( + &opctx, + &port, + rack_id, + switch_location, + &settings, + ) .await?; Ok(HttpResponseUpdatedNoContent {}) }; @@ -4753,6 +4832,7 @@ async fn networking_switch_port_apply_settings( method = DELETE, path = "/v1/system/hardware/switch-port/{port}/settings", tags = ["system/hardware"], + deprecated = true, }] async fn networking_switch_port_clear_settings( rqctx: RequestContext, @@ -4763,9 +4843,12 @@ async fn networking_switch_port_clear_settings( let handler = async { let nexus = &apictx.context.nexus; let port = path_params.into_inner().port; - let query = query_params.into_inner(); + let params::SwitchPortSelector { rack_id, switch_location } = + query_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - nexus.switch_port_clear_settings(&opctx, &port, &query).await?; + nexus + .switch_port_clear_settings(&opctx, &port, rack_id, switch_location) + .await?; Ok(HttpResponseUpdatedNoContent {}) }; apictx diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index b08057f6e22..a26e60060e1 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -128,6 +128,9 @@ snapshot_view GET /v1/snapshots/{snapshot} API operations found with tag "system/hardware" OPERATION ID METHOD URL PATH +networking_switch_port_active_configuration_clear DELETE /v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration +networking_switch_port_active_configuration_set PUT /v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration +networking_switch_port_active_configuration_view GET /v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration networking_switch_port_apply_settings POST /v1/system/hardware/switch-port/{port}/settings networking_switch_port_clear_settings DELETE /v1/system/hardware/switch-port/{port}/settings networking_switch_port_list GET /v1/system/hardware/switch-port diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index c5091c5a3bc..05be38ef5c7 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -1,8 +1,10 @@ API endpoints with no coverage in authz tests: probe_delete (delete "/experimental/v1/probes/{probe}") +networking_switch_port_active_configuration_clear (delete "/v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration") probe_list (get "/experimental/v1/probes") probe_view (get "/experimental/v1/probes/{probe}") ping (get "/v1/ping") +networking_switch_port_active_configuration_view (get "/v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration") networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") @@ -11,3 +13,4 @@ probe_create (post "/experimental/v1/probes") login_saml (post "/login/{silo_name}/saml/{provider_name}") login_local (post "/v1/login/{silo_name}/local") logout (post "/v1/logout") +networking_switch_port_active_configuration_set (put "/v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration") diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index ca20792bb84..26a8b2e52b8 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -12,7 +12,7 @@ use omicron_common::api::external::{ AddressLotKind, AllowedSourceIps, BfdMode, BgpPeerCombined, ByteCount, Hostname, IdentityMetadataCreateParams, IdentityMetadataUpdateParams, InstanceCpuCount, LinkFec, LinkSpeed, Name, NameOrId, PaginationOrder, - RouteDestination, RouteTarget, SemverVersion, UserId, + RouteDestination, RouteTarget, SemverVersion, SwitchLocation, UserId, }; use omicron_common::disk::DiskVariant; use oxnet::{IpNet, Ipv4Net, Ipv6Net}; @@ -1889,7 +1889,7 @@ pub struct SwitchPortSelector { pub rack_id: Uuid, /// A switch location to use when selecting switch ports. - pub switch_location: Name, + pub switch_location: SwitchLocation, } /// Select switch port interfaces by id. @@ -1913,6 +1913,19 @@ pub struct SwitchPortApplySettings { pub port_settings: NameOrId, } +/// Select a switch port by name +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortConfigurationSelector { + /// A rack id to use when selecting switch ports. + pub rack_id: Uuid, + + /// A switch location to use when selecting switch ports. + pub switch: SwitchLocation, + + /// A name to use when selecting switch ports. + pub port: Name, +} + // IMAGES /// The source of the underlying image. diff --git a/openapi/nexus.json b/openapi/nexus.json index f90f16b705a..86b30788c37 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -4195,6 +4195,169 @@ } } }, + "/v1/system/hardware/racks/{rack_id}/switch/{switch}/switch-port/{port}/configuration": { + "get": { + "tags": [ + "system/hardware" + ], + "summary": "View switch port configuration", + "operationId": "networking_switch_port_active_configuration_view", + "parameters": [ + { + "in": "path", + "name": "port", + "description": "A name to use when selecting switch ports.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "rack_id", + "description": "A rack id to use when selecting switch ports.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "switch", + "description": "A switch location to use when selecting switch ports.", + "required": true, + "schema": { + "$ref": "#/components/schemas/SwitchLocation" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortSettings" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "tags": [ + "system/hardware" + ], + "summary": "Set switch port configuration", + "operationId": "networking_switch_port_active_configuration_set", + "parameters": [ + { + "in": "path", + "name": "port", + "description": "A name to use when selecting switch ports.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "rack_id", + "description": "A rack id to use when selecting switch ports.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "switch", + "description": "A switch location to use when selecting switch ports.", + "required": true, + "schema": { + "$ref": "#/components/schemas/SwitchLocation" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPortApplySettings" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "system/hardware" + ], + "summary": "Clear switch port configuration", + "operationId": "networking_switch_port_active_configuration_clear", + "parameters": [ + { + "in": "path", + "name": "port", + "description": "A name to use when selecting switch ports.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + } + }, + { + "in": "path", + "name": "rack_id", + "description": "A rack id to use when selecting switch ports.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "switch", + "description": "A switch location to use when selecting switch ports.", + "required": true, + "schema": { + "$ref": "#/components/schemas/SwitchLocation" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/system/hardware/sleds": { "get": { "tags": [ @@ -4669,7 +4832,7 @@ "description": "A switch location to use when selecting switch ports.", "required": true, "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/SwitchLocation" } } ], @@ -4693,7 +4856,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "delete": { "tags": [ @@ -4727,7 +4891,7 @@ "description": "A switch location to use when selecting switch ports.", "required": true, "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/SwitchLocation" } } ], @@ -4741,7 +4905,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/v1/system/hardware/switch-port/{port}/status": { @@ -4777,7 +4942,7 @@ "description": "A switch location to use when selecting switch ports.", "required": true, "schema": { - "$ref": "#/components/schemas/Name" + "$ref": "#/components/schemas/SwitchLocation" } } ], From 4140aad8fa6e4cd10a3188037c724b8f54fe65f0 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 24 Oct 2024 17:06:38 +0000 Subject: [PATCH 35/37] add tests for new active-configuration endpoints --- nexus/tests/integration_tests/endpoints.rs | 35 +++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 85d9850e2ea..e38eb398f34 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -36,6 +36,7 @@ use omicron_common::api::external::Name; use omicron_common::api::external::NameOrId; use omicron_common::api::external::RouteDestination; use omicron_common::api::external::RouteTarget; +use omicron_common::api::external::SwitchLocation; use omicron_common::api::external::UserId; use omicron_common::api::external::VpcFirewallRuleUpdateParams; use omicron_test_utils::certificates::CertificateChain; @@ -486,19 +487,32 @@ pub static DEMO_CERTIFICATE_CREATE: Lazy = pub const DEMO_SWITCH_PORT_URL: &'static str = "/v1/system/hardware/switch-port"; + pub static DEMO_SWITCH_PORT_SETTINGS_APPLY_URL: Lazy = Lazy::new( || { format!( - "/v1/system/hardware/switch-port/qsfp7/settings?rack_id={}&switch_location={}", - uuid::Uuid::new_v4(), - "switch0", - ) + "/v1/system/hardware/switch-port/qsfp0/settings?rack_id={}&switch_location={}", + RACK_UUID, + "switch0", + ) }, ); pub static DEMO_SWITCH_PORT_SETTINGS: Lazy = Lazy::new(|| params::SwitchPortApplySettings { port_settings: NameOrId::Name("portofino".parse().unwrap()), }); + +pub static DEMO_SWITCH_PORT_ACTIVE_CONFIGURATION_URL: Lazy = Lazy::new( + || { + format!( + "/v1/system/hardware/racks/{}/switch/{}/switch-port/{}/configuration", + RACK_UUID, + SwitchLocation::Switch0, + "qsfp0", + ) + }, +); + /* TODO requires dpd access pub static DEMO_SWITCH_PORT_STATUS_URL: Lazy = Lazy::new(|| { format!( @@ -2363,6 +2377,19 @@ pub static VERIFY_ENDPOINTS: Lazy> = Lazy::new(|| { ], }, + VerifyEndpoint { + url: &DEMO_SWITCH_PORT_ACTIVE_CONFIGURATION_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + AllowedMethod::Delete, + AllowedMethod::Put( + serde_json::to_value(&*DEMO_SWITCH_PORT_SETTINGS).unwrap(), + ), + ], + }, + VerifyEndpoint { url: &DEMO_ADDRESS_LOTS_URL, visibility: Visibility::Public, From 7d8e38f1366d664640afb21451ac46d155b1520c Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Fri, 25 Oct 2024 21:40:36 +0000 Subject: [PATCH 36/37] regen authz txt --- nexus/tests/output/uncovered-authz-endpoints.txt | 2 -- nexus/tests/output/unexpected-authz-endpoints.txt | 2 -- 2 files changed, 4 deletions(-) diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index c45d7e6f759..c5091c5a3bc 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -1,11 +1,9 @@ API endpoints with no coverage in authz tests: probe_delete (delete "/experimental/v1/probes/{probe}") -networking_bgp_config_delete (delete "/v1/system/networking/bgp") probe_list (get "/experimental/v1/probes") probe_view (get "/experimental/v1/probes/{probe}") ping (get "/v1/ping") networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status") -networking_switch_port_configuration_view (get "/v1/system/networking/switch-port-settings/{configuration}") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") device_access_token (post "/device/token") diff --git a/nexus/tests/output/unexpected-authz-endpoints.txt b/nexus/tests/output/unexpected-authz-endpoints.txt index ecdc5ce6cf7..23235ecf641 100644 --- a/nexus/tests/output/unexpected-authz-endpoints.txt +++ b/nexus/tests/output/unexpected-authz-endpoints.txt @@ -2,5 +2,3 @@ API endpoints tested by unauthorized.rs but not found in the OpenAPI spec: PUT "/v1/system/update/repository?file_name=demo-repo.zip" GET "/v1/system/update/repository/1.0.0" DELETE "/v1/system/networking/address-lot/parkinglot" -GET "/v1/system/networking/switch-port-configuration/portofino" -DELETE "/v1/system/networking/bgp/as47" From 4f046351c7d5cef65b737a1aeab478a99826453a Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 31 Oct 2024 17:06:45 +0000 Subject: [PATCH 37/37] remove unused params --- nexus/types/src/external_api/params.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 57a66d09d72..77a3c524d6d 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1937,13 +1937,6 @@ pub struct BgpConfigCreate { pub checker: Option, } -/// Select a BGP status information by BGP config id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BgpStatusSelector { - /// A name or id of the BGP configuration to get status for - pub name_or_id: NameOrId, -} - /// Information about a bidirectional forwarding detection (BFD) session. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct BfdSessionEnable { @@ -2248,13 +2241,6 @@ pub struct ProbeCreate { pub ip_pool: Option, } -/// List probes with an optional name or id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct ProbeListSelector { - /// A name or id to use when selecting a probe. - pub name_or_id: Option, -} - /// A timeseries query string, written in the Oximeter query language. #[derive(Deserialize, JsonSchema, Serialize)] pub struct TimeseriesQuery {