diff --git a/nexus/db-model/src/ip_pool.rs b/nexus/db-model/src/ip_pool.rs index e587566713b..8211287248b 100644 --- a/nexus/db-model/src/ip_pool.rs +++ b/nexus/db-model/src/ip_pool.rs @@ -16,7 +16,7 @@ use db_macros::Resource; use diesel::Selectable; use ipnetwork::IpNetwork; use nexus_types::external_api::params; -use nexus_types::external_api::shared::{self, IpRange}; +use nexus_types::external_api::shared::IpRange; use nexus_types::external_api::views; use nexus_types::identity::Resource; use omicron_common::api::external; @@ -97,14 +97,6 @@ pub struct IpPoolResource { pub is_default: bool, } -impl From for shared::IpPoolResourceType { - fn from(typ: IpPoolResourceType) -> Self { - match typ { - IpPoolResourceType::Silo => Self::Silo, - } - } -} - /// Information required to delete an IP pool association. Comes from request /// params -- silo is a NameOrId and must be resolved to ID. pub struct IpPoolResourceDelete { @@ -113,12 +105,11 @@ pub struct IpPoolResourceDelete { pub resource_id: Uuid, } -impl From for views::IpPoolResource { +impl From for views::IpPoolSilo { fn from(assoc: IpPoolResource) -> Self { Self { ip_pool_id: assoc.ip_pool_id, - resource_type: assoc.resource_type.into(), - resource_id: assoc.resource_id, + silo_id: assoc.resource_id, is_default: assoc.is_default, } } diff --git a/nexus/db-queries/src/db/datastore/ip_pool.rs b/nexus/db-queries/src/db/datastore/ip_pool.rs index 151280d7b32..a77ac932ddb 100644 --- a/nexus/db-queries/src/db/datastore/ip_pool.rs +++ b/nexus/db-queries/src/db/datastore/ip_pool.rs @@ -681,29 +681,29 @@ mod test { assert_eq!(fleet_default_pool.identity.name.as_str(), "default"); - // unique index prevents second fleet-level default - let identity = IdentityMetadataCreateParams { - name: "another-fleet-default".parse().unwrap(), - description: "".to_string(), - }; - let second_default = datastore - .ip_pool_create(&opctx, IpPool::new(&identity)) - .await - .expect("Failed to create pool"); - let err = datastore - .ip_pool_associate_resource( - &opctx, - IpPoolResource { - ip_pool_id: second_default.id(), - resource_type: IpPoolResourceType::Fleet, - resource_id: *FLEET_ID, - is_default: true, - }, - ) - .await - .expect_err("Failed to fail to make IP pool fleet default"); - - assert_matches!(err, Error::ObjectAlreadyExists { .. }); + // // unique index prevents second fleet-level default + // let identity = IdentityMetadataCreateParams { + // name: "another-fleet-default".parse().unwrap(), + // description: "".to_string(), + // }; + // let second_default = datastore + // .ip_pool_create(&opctx, IpPool::new(&identity)) + // .await + // .expect("Failed to create pool"); + // let err = datastore + // .ip_pool_associate_resource( + // &opctx, + // IpPoolResource { + // ip_pool_id: second_default.id(), + // resource_type: IpPoolResourceType::Fleet, + // resource_id: *FLEET_ID, + // is_default: true, + // }, + // ) + // .await + // .expect_err("Failed to fail to make IP pool fleet default"); + + // assert_matches!(err, Error::ObjectAlreadyExists { .. }); // now test logic preferring most specific available default diff --git a/nexus/src/app/ip_pool.rs b/nexus/src/app/ip_pool.rs index 8324018b37e..7284facf724 100644 --- a/nexus/src/app/ip_pool.rs +++ b/nexus/src/app/ip_pool.rs @@ -91,31 +91,22 @@ impl super::Nexus { &self, opctx: &OpContext, pool_lookup: &lookup::IpPool<'_>, - assoc_create: ¶ms::IpPoolAssociationCreate, + silo_link: ¶ms::IpPoolSiloLink, ) -> CreateResult { let (.., authz_pool) = pool_lookup.lookup_for(authz::Action::Modify).await?; - let (resource_type, resource_id, is_default) = match assoc_create { - params::IpPoolAssociationCreate::Silo(assoc_silo) => { - let (silo,) = self - .silo_lookup(&opctx, assoc_silo.silo.clone())? - .lookup_for(authz::Action::Read) - .await?; - ( - db::model::IpPoolResourceType::Silo, - silo.id(), - assoc_silo.is_default, - ) - } - }; + let (silo,) = self + .silo_lookup(&opctx, silo_link.silo.clone())? + .lookup_for(authz::Action::Read) + .await?; self.db_datastore .ip_pool_associate_resource( opctx, db::model::IpPoolResource { ip_pool_id: authz_pool.id(), - resource_type, - resource_id, - is_default, + resource_type: db::model::IpPoolResourceType::Silo, + resource_id: silo.id(), + is_default: silo_link.is_default, }, ) .await diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 3ba6cf8d8aa..3042d398fe4 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -122,9 +122,9 @@ pub(crate) fn external_api() -> NexusApiDescription { // Operator-Accessible IP Pools API api.register(ip_pool_list)?; api.register(ip_pool_create)?; - api.register(ip_pool_association_list)?; - api.register(ip_pool_association_create)?; - api.register(ip_pool_association_delete)?; + api.register(ip_pool_silos_list)?; + api.register(ip_pool_silo_link)?; + api.register(ip_pool_silo_unlink)?; api.register(ip_pool_view)?; api.register(ip_pool_delete)?; api.register(ip_pool_update)?; @@ -1323,20 +1323,20 @@ async fn ip_pool_update( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } -/// List an IP pool's associated silo configuration +/// List an IP pool's linked silos #[endpoint { method = GET, path = "/v1/system/ip-pools/{pool}/silos", tags = ["system/networking"], }] -async fn ip_pool_association_list( +async fn ip_pool_silos_list( rqctx: RequestContext>, path_params: Path, // paginating by resource_id because they're unique per pool. most robust // option would be to paginate by a composite key representing the (pool, // resource_type, resource) query_params: Query, -) -> Result>, HttpError> { +) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; @@ -1358,23 +1358,23 @@ async fn ip_pool_association_list( Ok(HttpResponseOk(ScanById::results_page( &query, assocs, - &|_, x: &views::IpPoolResource| x.resource_id, + &|_, x: &views::IpPoolSilo| x.silo_id, )?)) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } -/// Associate an IP Pool with a silo +/// Make an IP pool available within a silo #[endpoint { method = POST, path = "/v1/system/ip-pools/{pool}/silos", tags = ["system/networking"], }] -async fn ip_pool_association_create( +async fn ip_pool_silo_link( rqctx: RequestContext>, path_params: Path, - resource_assoc: TypedBody, -) -> Result, HttpError> { + resource_assoc: TypedBody, +) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; @@ -1396,10 +1396,10 @@ async fn ip_pool_association_create( path = "/v1/system/ip-pools/{pool}/silos", tags = ["system/networking"], }] -async fn ip_pool_association_delete( +async fn ip_pool_silo_unlink( rqctx: RequestContext>, path_params: Path, - query_params: Query, + query_params: Query, ) -> Result { let apictx = rqctx.context(); let handler = async { diff --git a/nexus/test-utils/src/resource_helpers.rs b/nexus/test-utils/src/resource_helpers.rs index b575e1b3787..ea2ccc12591 100644 --- a/nexus/test-utils/src/resource_helpers.rs +++ b/nexus/test-utils/src/resource_helpers.rs @@ -145,14 +145,14 @@ pub async fn create_ip_pool( .await; // make pool available for use anywhere in fleet - let _assoc: views::IpPoolResource = object_create( - client, - &format!("/v1/system/ip-pools/{pool_name}/associations"), - ¶ms::IpPoolAssociationCreate::Fleet(params::IpPoolAssociateFleet { - is_default: false, - }), - ) - .await; + // let _assoc: views::IpPoolSilo = object_create( + // client, + // &format!("/v1/system/ip-pools/{pool_name}/associations"), + // ¶ms::IpPoolAssociationCreate::Fleet(params::IpPoolAssociateFleet { + // is_default: false, + // }), + // ) + // .await; // TODO: associate with fleet as a non-default like before? let range = populate_ip_pool(client, pool_name, ip_range).await; diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index e52d04a5914..ff40d56019a 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -522,12 +522,12 @@ lazy_static! { description: Some(String::from("a new IP pool")), }, }; - pub static ref DEMO_IP_POOL_ASSOC_URL: String = format!("{}/associations", *DEMO_IP_POOL_URL); - pub static ref DEMO_IP_POOL_ASSOC_BODY: params::IpPoolAssociationCreate = - params::IpPoolAssociationCreate::Silo(params::IpPoolAssociateSilo { + pub static ref DEMO_IP_POOL_ASSOC_URL: String = format!("{}/silos", *DEMO_IP_POOL_URL); + pub static ref DEMO_IP_POOL_ASSOC_BODY: params::IpPoolSiloLink = + params::IpPoolSiloLink { silo: NameOrId::Id(DEFAULT_SILO.identity().id), is_default: false, - }); + }; pub static ref DEMO_IP_POOL_RANGE: IpRange = IpRange::V4(Ipv4Range::new( std::net::Ipv4Addr::new(10, 0, 0, 0), std::net::Ipv4Addr::new(10, 0, 0, 255), diff --git a/nexus/tests/integration_tests/instances.rs b/nexus/tests/integration_tests/instances.rs index 346ee328395..5c0fda03daf 100644 --- a/nexus/tests/integration_tests/instances.rs +++ b/nexus/tests/integration_tests/instances.rs @@ -3613,12 +3613,11 @@ async fn test_instance_ephemeral_ip_from_correct_pool( }, ) .await; - let params = - params::IpPoolAssociationCreate::Silo(params::IpPoolAssociateSilo { - silo: NameOrId::Id(DEFAULT_SILO.id()), - is_default: true, - }); - let assoc_url = format!("/v1/system/ip-pools/{pool_name}/associations"); + let params = params::IpPoolSiloLink { + silo: NameOrId::Id(DEFAULT_SILO.id()), + is_default: true, + }; + let assoc_url = format!("/v1/system/ip-pools/{pool_name}/silo"); let _ = NexusRequest::objects_post(client, &assoc_url, ¶ms) .authn_as(AuthnMode::PrivilegedUser) .execute() diff --git a/nexus/tests/integration_tests/ip_pools.rs b/nexus/tests/integration_tests/ip_pools.rs index 9cf1155337c..bd7fe6033b3 100644 --- a/nexus/tests/integration_tests/ip_pools.rs +++ b/nexus/tests/integration_tests/ip_pools.rs @@ -26,13 +26,12 @@ use nexus_types::external_api::params::InstanceDiskAttachment; use nexus_types::external_api::params::InstanceNetworkInterfaceAttachment; use nexus_types::external_api::params::IpPoolCreate; use nexus_types::external_api::params::IpPoolUpdate; -use nexus_types::external_api::shared::IpPoolResourceType; use nexus_types::external_api::shared::IpRange; use nexus_types::external_api::shared::Ipv4Range; use nexus_types::external_api::shared::Ipv6Range; use nexus_types::external_api::views::IpPool; use nexus_types::external_api::views::IpPoolRange; -use nexus_types::external_api::views::IpPoolResource; +use nexus_types::external_api::views::IpPoolSilo; use nexus_types::external_api::views::Silo; use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::NameOrId; @@ -357,17 +356,16 @@ async fn test_ip_pool_with_silo(cptestctx: &ControlPlaneTestContext) { // expect 404 on association if the specified silo doesn't exist let nonexistent_silo_id = Uuid::new_v4(); - let params = - params::IpPoolAssociationCreate::Silo(params::IpPoolAssociateSilo { - silo: NameOrId::Id(nonexistent_silo_id), - is_default: false, - }); + let params = params::IpPoolSiloLink { + silo: NameOrId::Id(nonexistent_silo_id), + is_default: false, + }; let error = NexusRequest::new( RequestBuilder::new( client, Method::POST, - "/v1/system/ip-pools/p0/associations", + "/v1/system/ip-pools/p0/silos", ) .body(Some(¶ms)) .expect_status(Some(StatusCode::NOT_FOUND)), @@ -385,15 +383,13 @@ async fn test_ip_pool_with_silo(cptestctx: &ControlPlaneTestContext) { ); // associate by name with silo that exists - let params = - params::IpPoolAssociationCreate::Silo(params::IpPoolAssociateSilo { - // TODO: this is probably not the best silo ID to use - silo: NameOrId::Name(cptestctx.silo_name.clone()), - is_default: false, - }); - let _: IpPoolResource = - object_create(client, "/v1/system/ip-pools/p0/associations", ¶ms) - .await; + let params = params::IpPoolSiloLink { + // TODO: this is probably not the best silo ID to use + silo: NameOrId::Name(cptestctx.silo_name.clone()), + is_default: false, + }; + let _: IpPoolSilo = + object_create(client, "/v1/system/ip-pools/p0/silos", ¶ms).await; // get silo ID so we can test association by ID as well let silo_url = format!("/v1/system/silos/{}", cptestctx.silo_name); @@ -404,12 +400,8 @@ async fn test_ip_pool_with_silo(cptestctx: &ControlPlaneTestContext) { let silo_id = silo.identity.id; let assocs_p0 = get_associations(client, "p0").await; - let silo_assoc = IpPoolResource { - ip_pool_id: p0.identity.id, - resource_type: IpPoolResourceType::Silo, - resource_id: silo_id, - is_default: false, - }; + let silo_assoc = + IpPoolSilo { ip_pool_id: p0.identity.id, silo_id, is_default: false }; assert_eq!(assocs_p0.items.len(), 1); assert_eq!(assocs_p0.items[0], silo_assoc); @@ -417,21 +409,19 @@ async fn test_ip_pool_with_silo(cptestctx: &ControlPlaneTestContext) { // TODO: confirm dissociation // associate same silo to other pool by ID - let params = - params::IpPoolAssociationCreate::Silo(params::IpPoolAssociateSilo { - silo: NameOrId::Id(silo.identity.id), - is_default: false, - }); - let _: IpPoolResource = - object_create(client, "/v1/system/ip-pools/p1/associations", ¶ms) - .await; + let params = params::IpPoolSiloLink { + silo: NameOrId::Id(silo.identity.id), + is_default: false, + }; + let _: IpPoolSilo = + object_create(client, "/v1/system/ip-pools/p1/silo", ¶ms).await; // association should look the same as the other one, except different pool ID let assocs_p1 = get_associations(client, "p1").await; assert_eq!(assocs_p1.items.len(), 1); assert_eq!( assocs_p1.items[0], - IpPoolResource { ip_pool_id: p1.identity.id, ..silo_assoc } + IpPoolSilo { ip_pool_id: p1.identity.id, ..silo_assoc } ); // TODO: associating a resource that is already associated should be a noop @@ -486,10 +476,10 @@ fn get_names(pools: Vec) -> Vec { async fn get_associations( client: &ClientTestContext, id: &str, -) -> ResultsPage { - objects_list_page_authz::( +) -> ResultsPage { + objects_list_page_authz::( client, - &format!("/v1/system/ip-pools/{}/associations", id), + &format!("/v1/system/ip-pools/{}/silos", id), ) .await } @@ -866,16 +856,16 @@ async fn test_ip_pool_list_usable_by_project( // add to fleet since we can't add to project yet // TODO: could do silo, might as well? need the ID, though. at least // until I make it so you can specify the resource by name - let params = - params::IpPoolAssociationCreate::Fleet(params::IpPoolAssociateFleet { - is_default: false, - }); - let _: IpPoolResource = object_create( - client, - &format!("/v1/system/ip-pools/{mypool_name}/associations"), - ¶ms, - ) - .await; + // let params = + // params::IpPoolAssociationCreate::Fleet(params::IpPoolAssociateFleet { + // is_default: false, + // }); + // let _: IpPoolResource = object_create( + // client, + // &format!("/v1/system/ip-pools/{mypool_name}/associations"), + // ¶ms, + // ) + // .await; // Add an IP range to mypool let mypool_range = IpRange::V4( diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 1a58f30514e..d65f1dcd64e 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -132,9 +132,6 @@ system_metric GET /v1/system/metrics/{metric_nam API operations found with tag "system/networking" OPERATION ID METHOD URL PATH -ip_pool_association_create POST /v1/system/ip-pools/{pool}/associations -ip_pool_association_delete DELETE /v1/system/ip-pools/{pool}/associations -ip_pool_association_list GET /v1/system/ip-pools/{pool}/associations ip_pool_create POST /v1/system/ip-pools ip_pool_delete DELETE /v1/system/ip-pools/{pool} ip_pool_list GET /v1/system/ip-pools @@ -145,6 +142,9 @@ ip_pool_service_range_add POST /v1/system/ip-pools-service/ra ip_pool_service_range_list GET /v1/system/ip-pools-service/ranges ip_pool_service_range_remove POST /v1/system/ip-pools-service/ranges/remove ip_pool_service_view GET /v1/system/ip-pools-service +ip_pool_silo_link POST /v1/system/ip-pools/{pool}/silos +ip_pool_silo_unlink DELETE /v1/system/ip-pools/{pool}/silos +ip_pool_silos_list GET /v1/system/ip-pools/{pool}/silos ip_pool_update PUT /v1/system/ip-pools/{pool} ip_pool_view GET /v1/system/ip-pools/{pool} networking_address_lot_block_list GET /v1/system/networking/address-lot/{address_lot}/blocks diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index d85ef52386d..9c91cf3e828 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -758,26 +758,13 @@ pub struct IpPoolUpdate { } #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPoolAssociateSilo { +pub struct IpPoolSiloLink { pub silo: NameOrId, pub is_default: bool, } -/// Parameters for associating an IP pool with a silo #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "resource_type", rename_all = "snake_case")] -pub enum IpPoolAssociationCreate { - Silo(IpPoolAssociateSilo), -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPoolAssociationDelete { - pub silo: NameOrId, -} - -// technically these are not params, but they are used with params -#[derive(Clone, Debug)] -pub struct IpPoolSiloAssociationDelete { +pub struct IpPoolSiloUnlink { pub silo: NameOrId, } diff --git a/nexus/types/src/external_api/shared.rs b/nexus/types/src/external_api/shared.rs index dcd7709f61c..a4c5ae1e629 100644 --- a/nexus/types/src/external_api/shared.rs +++ b/nexus/types/src/external_api/shared.rs @@ -245,12 +245,6 @@ pub enum UpdateableComponentType { HostOmicron, } -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum IpPoolResourceType { - Silo, -} - /// Properties that uniquely identify an Oxide hardware component #[derive( Clone, diff --git a/nexus/types/src/external_api/views.rs b/nexus/types/src/external_api/views.rs index 29df547e2cd..8dca604e7bc 100644 --- a/nexus/types/src/external_api/views.rs +++ b/nexus/types/src/external_api/views.rs @@ -248,12 +248,9 @@ pub struct IpPool { } #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct IpPoolResource { - // TODO: is including the pool ID redundant? it's convenient to have and - // makes this response a cohesive whole +pub struct IpPoolSilo { pub ip_pool_id: Uuid, - pub resource_type: shared::IpPoolResourceType, - pub resource_id: Uuid, + pub silo_id: Uuid, pub is_default: bool, } diff --git a/openapi/nexus.json b/openapi/nexus.json index 3f0611abad3..8b03f23605f 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -4685,13 +4685,14 @@ } } }, - "/v1/system/ip-pools/{pool}/associations": { + "/v1/system/ip-pools/{pool}/ranges": { "get": { "tags": [ "system/networking" ], - "summary": "List IP pool resource associations", - "operationId": "ip_pool_association_list", + "summary": "List ranges for an IP pool", + "description": "List ranges for an IP pool. Ranges are ordered by their first address.", + "operationId": "ip_pool_range_list", "parameters": [ { "in": "path", @@ -4721,13 +4722,6 @@ "nullable": true, "type": "string" } - }, - { - "in": "query", - "name": "sort_by", - "schema": { - "$ref": "#/components/schemas/IdSortMode" - } } ], "responses": { @@ -4736,7 +4730,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IpPoolResourceResultsPage" + "$ref": "#/components/schemas/IpPoolRangeResultsPage" } } } @@ -4751,13 +4745,15 @@ "x-dropshot-pagination": { "required": [] } - }, + } + }, + "/v1/system/ip-pools/{pool}/ranges/add": { "post": { "tags": [ "system/networking" ], - "summary": "Associate an IP Pool with a silo or the fleet", - "operationId": "ip_pool_association_create", + "summary": "Add a range to an IP pool", + "operationId": "ip_pool_range_add", "parameters": [ { "in": "path", @@ -4773,7 +4769,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IpPoolAssociationCreate" + "$ref": "#/components/schemas/IpRange" } } }, @@ -4785,7 +4781,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IpPoolResource" + "$ref": "#/components/schemas/IpPoolRange" } } } @@ -4797,13 +4793,15 @@ "$ref": "#/components/responses/Error" } } - }, - "delete": { + } + }, + "/v1/system/ip-pools/{pool}/ranges/remove": { + "post": { "tags": [ "system/networking" ], - "summary": "Remove an IP pool's association with a silo or project", - "operationId": "ip_pool_association_delete", + "summary": "Remove a range from an IP pool", + "operationId": "ip_pool_range_remove", "parameters": [ { "in": "path", @@ -4813,23 +4811,18 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } - }, - { - "in": "query", - "name": "resource_type", - "required": true, - "schema": { - "$ref": "#/components/schemas/IpPoolResourceType" - } - }, - { - "in": "query", - "name": "silo", - "schema": { - "$ref": "#/components/schemas/NameOrId" - } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IpRange" + } + } + }, + "required": true + }, "responses": { "204": { "description": "resource updated" @@ -4843,14 +4836,13 @@ } } }, - "/v1/system/ip-pools/{pool}/ranges": { + "/v1/system/ip-pools/{pool}/silos": { "get": { "tags": [ "system/networking" ], - "summary": "List ranges for an IP pool", - "description": "List ranges for an IP pool. Ranges are ordered by their first address.", - "operationId": "ip_pool_range_list", + "summary": "List an IP pool's linked silos", + "operationId": "ip_pool_silos_list", "parameters": [ { "in": "path", @@ -4880,6 +4872,13 @@ "nullable": true, "type": "string" } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/IdSortMode" + } } ], "responses": { @@ -4888,7 +4887,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IpPoolRangeResultsPage" + "$ref": "#/components/schemas/IpPoolSiloResultsPage" } } } @@ -4903,15 +4902,13 @@ "x-dropshot-pagination": { "required": [] } - } - }, - "/v1/system/ip-pools/{pool}/ranges/add": { + }, "post": { "tags": [ "system/networking" ], - "summary": "Add a range to an IP pool", - "operationId": "ip_pool_range_add", + "summary": "Make an IP pool available within a silo", + "operationId": "ip_pool_silo_link", "parameters": [ { "in": "path", @@ -4927,7 +4924,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IpRange" + "$ref": "#/components/schemas/IpPoolSiloLink" } } }, @@ -4939,7 +4936,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IpPoolRange" + "$ref": "#/components/schemas/IpPoolSilo" } } } @@ -4951,15 +4948,13 @@ "$ref": "#/components/responses/Error" } } - } - }, - "/v1/system/ip-pools/{pool}/ranges/remove": { - "post": { + }, + "delete": { "tags": [ "system/networking" ], - "summary": "Remove a range from an IP pool", - "operationId": "ip_pool_range_remove", + "summary": "Remove an IP pool's association with a silo or project", + "operationId": "ip_pool_silo_unlink", "parameters": [ { "in": "path", @@ -4969,18 +4964,16 @@ "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "query", + "name": "silo", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IpRange" - } - } - }, - "required": true - }, "responses": { "204": { "description": "resource updated" @@ -11902,51 +11895,6 @@ "time_modified" ] }, - "IpPoolAssociationCreate": { - "description": "Parameters for associating an IP pool with a resource (fleet, silo)", - "oneOf": [ - { - "type": "object", - "properties": { - "is_default": { - "type": "boolean" - }, - "resource_type": { - "type": "string", - "enum": [ - "silo" - ] - }, - "silo": { - "$ref": "#/components/schemas/NameOrId" - } - }, - "required": [ - "is_default", - "resource_type", - "silo" - ] - }, - { - "type": "object", - "properties": { - "is_default": { - "type": "boolean" - }, - "resource_type": { - "type": "string", - "enum": [ - "fleet" - ] - } - }, - "required": [ - "is_default", - "resource_type" - ] - } - ] - }, "IpPoolCreate": { "description": "Create-time parameters for an `IpPool`", "type": "object", @@ -12010,7 +11958,28 @@ "items" ] }, - "IpPoolResource": { + "IpPoolResultsPage": { + "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/IpPool" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "IpPoolSilo": { "type": "object", "properties": { "ip_pool_id": { @@ -12020,50 +11989,33 @@ "is_default": { "type": "boolean" }, - "resource_id": { + "silo_id": { "type": "string", "format": "uuid" - }, - "resource_type": { - "$ref": "#/components/schemas/IpPoolResourceType" } }, "required": [ "ip_pool_id", "is_default", - "resource_id", - "resource_type" + "silo_id" ] }, - "IpPoolResourceResultsPage": { - "description": "A single page of results", + "IpPoolSiloLink": { "type": "object", "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/IpPoolResource" - } + "is_default": { + "type": "boolean" }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" + "silo": { + "$ref": "#/components/schemas/NameOrId" } }, "required": [ - "items" - ] - }, - "IpPoolResourceType": { - "type": "string", - "enum": [ - "fleet", + "is_default", "silo" ] }, - "IpPoolResultsPage": { + "IpPoolSiloResultsPage": { "description": "A single page of results", "type": "object", "properties": { @@ -12071,7 +12023,7 @@ "description": "list of items on this page of results", "type": "array", "items": { - "$ref": "#/components/schemas/IpPool" + "$ref": "#/components/schemas/IpPoolSilo" } }, "next_page": {