Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions nexus/external-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use openapiv3::OpenAPI;
mod v2025112000;
mod v2025120300;
mod v2025121200;
mod v2025122300;

api_versions!([
// API versions are in the format YYYYMMDDNN.0.0, defined below as
Expand Down Expand Up @@ -66,6 +67,7 @@ api_versions!([
// | date-based version should be at the top of the list.
// v
// (next_yyyymmddnn, IDENT),
(2026010100, SILO_PROJECT_IP_VERSION_AND_POOL_TYPE),
(2025122300, IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS),
(2025121200, BGP_PEER_COLLISION_STATE),
(2025120300, LOCAL_STORAGE),
Expand Down Expand Up @@ -490,9 +492,36 @@ pub trait NexusExternalApi {
/// can have at most one default pool. IPs are allocated from the default
/// pool when users ask for one without specifying a pool.
#[endpoint {
operation_id = "silo_ip_pool_list",
method = GET,
path = "/v1/system/silos/{silo}/ip-pools",
tags = ["system/silos"],
versions = ..VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE,
}]
async fn v2025122300_silo_ip_pool_list(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::SiloPath>,
query_params: Query<PaginatedByNameOrId>,
) -> Result<HttpResponseOk<ResultsPage<v2025122300::SiloIpPool>>, HttpError>
{
let page =
Self::silo_ip_pool_list(rqctx, path_params, query_params).await?.0;
Ok(HttpResponseOk(ResultsPage {
items: page.items.into_iter().map(Into::into).collect(),
next_page: page.next_page,
}))
}

/// List IP pools linked to silo
///
/// Linked IP pools are available to users in the specified silo. A silo
/// can have at most one default pool. IPs are allocated from the default
/// pool when users ask for one without specifying a pool.
#[endpoint {
method = GET,
path = "/v1/system/silos/{silo}/ip-pools",
tags = ["system/silos"],
versions = VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE..,
}]
async fn silo_ip_pool_list(
rqctx: RequestContext<Self::Context>,
Expand Down Expand Up @@ -941,20 +970,60 @@ pub trait NexusExternalApi {

/// List IP pools
#[endpoint {
operation_id = "project_ip_pool_list",
method = GET,
path = "/v1/ip-pools",
tags = ["projects"],
versions = ..VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE,
}]
async fn v2025122300_project_ip_pool_list(
rqctx: RequestContext<Self::Context>,
query_params: Query<PaginatedByNameOrId>,
) -> Result<HttpResponseOk<ResultsPage<v2025122300::SiloIpPool>>, HttpError>
{
let page = Self::project_ip_pool_list(rqctx, query_params).await?.0;
Ok(HttpResponseOk(ResultsPage {
items: page.items.into_iter().map(Into::into).collect(),
next_page: page.next_page,
}))
}

/// List IP pools
#[endpoint {
method = GET,
path = "/v1/ip-pools",
tags = ["projects"],
versions = VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE..,
}]
async fn project_ip_pool_list(
rqctx: RequestContext<Self::Context>,
query_params: Query<PaginatedByNameOrId>,
) -> Result<HttpResponseOk<ResultsPage<views::SiloIpPool>>, HttpError>;

/// Fetch IP pool
#[endpoint {
operation_id = "project_ip_pool_view",
method = GET,
path = "/v1/ip-pools/{pool}",
tags = ["projects"],
versions = ..VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE,
}]
async fn v2025122300_project_ip_pool_view(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::IpPoolPath>,
) -> Result<HttpResponseOk<v2025122300::SiloIpPool>, HttpError> {
match Self::project_ip_pool_view(rqctx, path_params).await {
Ok(HttpResponseOk(pool)) => Ok(HttpResponseOk(pool.into())),
Err(e) => Err(e),
}
}

/// Fetch IP pool
#[endpoint {
method = GET,
path = "/v1/ip-pools/{pool}",
tags = ["projects"],
versions = VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE..,
}]
async fn project_ip_pool_view(
rqctx: RequestContext<Self::Context>,
Expand Down
46 changes: 46 additions & 0 deletions nexus/external-api/src/v2025122300.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Nexus external types that changed from 2025122300 to 2026010100.
//!
//! Version 2025122300 types (before `ip_version` and `pool_type` were added
//! to `SiloIpPool` responses).
//!
//! Key differences from newer API versions:
//! - [`SiloIpPool`] doesn't have `ip_version` or `pool_type` fields.
//! Newer versions include these fields to indicate the IP version
//! and pool type (unicast or multicast) of the pool.
//!
//! Affected endpoints:
//! - `GET /v1/ip-pools` (project_ip_pool_list)
//! - `GET /v1/ip-pools/{pool}` (project_ip_pool_view)
//! - `GET /v1/system/silos/{silo}/ip-pools` (silo_ip_pool_list)
//!
//! [`SiloIpPool`]: self::SiloIpPool

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use nexus_types::external_api::views;
use omicron_common::api::external::IdentityMetadata;

/// An IP pool in the context of a silo (pre-2026010100 API version).
///
/// This version does not include `ip_version` or `pool_type` fields.
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct SiloIpPool {
#[serde(flatten)]
pub identity: IdentityMetadata,

/// When a pool is the default for a silo, floating IPs and instance
/// ephemeral IPs will come from that pool when no other pool is specified.
/// There can be at most one default for a given silo.
pub is_default: bool,
}

impl From<views::SiloIpPool> for SiloIpPool {
fn from(new: views::SiloIpPool) -> SiloIpPool {
SiloIpPool { identity: new.identity, is_default: new.is_default }
}
}
6 changes: 6 additions & 0 deletions nexus/src/external_api/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,8 @@ impl NexusExternalApi for NexusExternalApiImpl {
.map(|(pool, silo_link)| views::SiloIpPool {
identity: pool.identity(),
is_default: silo_link.is_default,
ip_version: pool.ip_version.into(),
pool_type: pool.pool_type.into(),
})
.collect();

Expand Down Expand Up @@ -1643,6 +1645,8 @@ impl NexusExternalApi for NexusExternalApiImpl {
.map(|(pool, silo_link)| views::SiloIpPool {
identity: pool.identity(),
is_default: silo_link.is_default,
ip_version: pool.ip_version.into(),
pool_type: pool.pool_type.into(),
})
.collect();
Ok(HttpResponseOk(ScanByNameOrId::results_page(
Expand Down Expand Up @@ -1673,6 +1677,8 @@ impl NexusExternalApi for NexusExternalApiImpl {
Ok(HttpResponseOk(views::SiloIpPool {
identity: pool.identity(),
is_default: silo_link.is_default,
ip_version: pool.ip_version.into(),
pool_type: pool.pool_type.into(),
}))
};
apictx
Expand Down
8 changes: 7 additions & 1 deletion nexus/types/src/external_api/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ pub struct IpPool {
pub identity: IdentityMetadata,
/// The IP version for the pool.
pub ip_version: IpVersion,
/// Type of IP pool (unicast or multicast)
/// Type of IP pool (unicast or multicast).
pub pool_type: shared::IpPoolType,
}

Expand Down Expand Up @@ -429,6 +429,12 @@ pub struct SiloIpPool {
/// (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4
/// default pools total.
pub is_default: bool,

/// The IP version for the pool.
pub ip_version: IpVersion,

/// Type of IP pool (unicast or multicast).
pub pool_type: shared::IpPoolType,
}

/// A link between an IP pool and a silo that allows one to allocate IPs from
Expand Down
Loading
Loading