From 7bccb1bd26096a34b10e0bf3a1566cacc3a2e18a Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Mon, 8 Jul 2024 20:58:12 +0000 Subject: [PATCH] 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 2470a58ef6..38fb90e58c 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 fe65f53611..8289dab14c 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 bf480893ae..be5d247597 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 83a2956698..67f34b5b16 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 fe25daf72e..fc5a58fc0f 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 85888975d6..921e5f68c7 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 b49d7d4399..f63bae38c4 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 1d7018e271..92d70de2e0 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 68b7fdfdc4..c886196738 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 39e5b76448..c5091c5a3b 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 cd05058762..23235ecf64 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 232028f090..336f044070 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 f2676f80b2..854c01053b 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": {