Skip to content

Commit a8b0bea

Browse files
[nexus] Clean up API versioning: chain conversions, fix module docs
This PR refactors a set of versioned type conversions to properly follow the [API versioning guide's](https://github.com/oxidecomputer/dropshot-api-manager/blob/main/guides/new-version.md) recommendations, including chaining through consecutive versions, ordering conventions, and aligning types to correctly match the ranges and where they should be defined. Changes: - Chain InstanceCreate conversions: v2025122300 → v2026010300 → v2026010500 → params - Update endpoint handlers to delegate through the version chain - Add per-section validity docs for modules where types have different supersession versions - Remove empty v2026011501.rs after types moved to v2026010500
1 parent 1121324 commit a8b0bea

File tree

9 files changed

+607
-522
lines changed

9 files changed

+607
-522
lines changed

nexus/external-api/src/lib.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ mod v2025122300;
4141
mod v2026010100;
4242
mod v2026010300;
4343
mod v2026010500;
44-
mod v2026011501;
4544

4645
#[cfg(test)]
4746
mod test_utils;
@@ -1610,8 +1609,8 @@ pub trait NexusExternalApi {
16101609
floating_params: TypedBody<v2026010300::FloatingIpCreate>,
16111610
) -> Result<HttpResponseCreated<views::FloatingIp>, HttpError> {
16121611
let floating_params =
1613-
floating_params.try_map(v2026011501::FloatingIpCreate::try_from)?;
1614-
Self::v2026011501_floating_ip_create(
1612+
floating_params.try_map(v2026010500::FloatingIpCreate::try_from)?;
1613+
Self::v2026010500_floating_ip_create(
16151614
rqctx,
16161615
query_params,
16171616
floating_params,
@@ -1627,10 +1626,10 @@ pub trait NexusExternalApi {
16271626
tags = ["floating-ips"],
16281627
versions = VERSION_POOL_SELECTION_ENUMS..VERSION_RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR,
16291628
}]
1630-
async fn v2026011501_floating_ip_create(
1629+
async fn v2026010500_floating_ip_create(
16311630
rqctx: RequestContext<Self::Context>,
16321631
query_params: Query<params::ProjectSelector>,
1633-
floating_params: TypedBody<v2026011501::FloatingIpCreate>,
1632+
floating_params: TypedBody<v2026010500::FloatingIpCreate>,
16341633
) -> Result<HttpResponseCreated<views::FloatingIp>, HttpError> {
16351634
Self::floating_ip_create(
16361635
rqctx,
@@ -2172,7 +2171,7 @@ pub trait NexusExternalApi {
21722171
query_params: Query<params::ProjectSelector>,
21732172
new_instance: TypedBody<v2025112000::InstanceCreate>,
21742173
) -> Result<HttpResponseCreated<Instance>, HttpError> {
2175-
Self::v2025121200_instance_create(
2174+
Self::v2025120300_instance_create(
21762175
rqctx,
21772176
query_params,
21782177
new_instance.map(Into::into),
@@ -2188,12 +2187,12 @@ pub trait NexusExternalApi {
21882187
tags = ["instances"],
21892188
versions = VERSION_LOCAL_STORAGE..VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS,
21902189
}]
2191-
async fn v2025121200_instance_create(
2190+
async fn v2025120300_instance_create(
21922191
rqctx: RequestContext<Self::Context>,
21932192
query_params: Query<params::ProjectSelector>,
2194-
new_instance: TypedBody<v2025121200::InstanceCreate>,
2193+
new_instance: TypedBody<v2025120300::InstanceCreate>,
21952194
) -> Result<HttpResponseCreated<Instance>, HttpError> {
2196-
Self::v2026010100_instance_create(
2195+
Self::v2025122300_instance_create(
21972196
rqctx,
21982197
query_params,
21992198
new_instance.map(Into::into),
@@ -2210,13 +2209,17 @@ pub trait NexusExternalApi {
22102209
versions =
22112210
VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS..VERSION_DUAL_STACK_NICS,
22122211
}]
2213-
async fn v2026010100_instance_create(
2212+
async fn v2025122300_instance_create(
22142213
rqctx: RequestContext<Self::Context>,
22152214
query_params: Query<params::ProjectSelector>,
2216-
new_instance: TypedBody<v2026010100::InstanceCreate>,
2215+
new_instance: TypedBody<v2025122300::InstanceCreate>,
22172216
) -> Result<HttpResponseCreated<Instance>, HttpError> {
2218-
let new_instance = new_instance.try_map(TryInto::try_into)?;
2219-
Self::instance_create(rqctx, query_params, new_instance).await
2217+
Self::v2026010300_instance_create(
2218+
rqctx,
2219+
query_params,
2220+
new_instance.try_map(TryInto::try_into)?,
2221+
)
2222+
.await
22202223
}
22212224

22222225
/// Create instance
@@ -2232,8 +2235,12 @@ pub trait NexusExternalApi {
22322235
query_params: Query<params::ProjectSelector>,
22332236
new_instance: TypedBody<v2026010300::InstanceCreate>,
22342237
) -> Result<HttpResponseCreated<Instance>, HttpError> {
2235-
let new_instance = new_instance.try_map(TryInto::try_into)?;
2236-
Self::instance_create(rqctx, query_params, new_instance).await
2238+
Self::v2026010500_instance_create(
2239+
rqctx,
2240+
query_params,
2241+
new_instance.try_map(TryInto::try_into)?,
2242+
)
2243+
.await
22372244
}
22382245

22392246
/// Create instance

nexus/external-api/src/v2025112000.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
//! Nexus external types that changed from 2025112000 to 2025120300
5+
//! Nexus external types that changed from 2025112000 (INITIAL) to 2025120300
6+
//! (LOCAL_STORAGE).
67
7-
use crate::v2025121200;
8+
use crate::v2025120300;
89
use crate::v2026010100;
910
use nexus_types::external_api::params;
1011
use omicron_common::api::external;
@@ -220,9 +221,9 @@ pub struct InstanceCreate {
220221
/// By default, all instances have outbound connectivity, but no inbound
221222
/// connectivity. These external addresses can be used to provide a fixed,
222223
/// known IP address for making inbound connections to the instance.
223-
// Delegates through v2025121200 → params::ExternalIpCreate
224+
// Delegates through v2025120300 → params::ExternalIpCreate
224225
#[serde(default)]
225-
pub external_ips: Vec<v2025121200::ExternalIpCreate>,
226+
pub external_ips: Vec<v2025120300::ExternalIpCreate>,
226227

227228
/// The multicast groups this instance should join.
228229
///
@@ -302,9 +303,9 @@ pub struct InstanceCreate {
302303
pub cpu_platform: Option<external::InstanceCpuPlatform>,
303304
}
304305

305-
impl From<InstanceCreate> for v2025121200::InstanceCreate {
306-
fn from(old: InstanceCreate) -> v2025121200::InstanceCreate {
307-
v2025121200::InstanceCreate {
306+
impl From<InstanceCreate> for v2025120300::InstanceCreate {
307+
fn from(old: InstanceCreate) -> v2025120300::InstanceCreate {
308+
v2025120300::InstanceCreate {
308309
identity: old.identity,
309310
ncpus: old.ncpus,
310311
memory: old.memory,

nexus/external-api/src/v2025120300.rs

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,34 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
//! Nexus external types that changed from 2025120300 to 2025121200
5+
//! Nexus external types introduced in 2025120300 (LOCAL_STORAGE).
6+
//!
7+
//! This version introduced local storage support for instances.
8+
//!
9+
//! ## Instance Creation Types
10+
//!
11+
//! Valid until 2025122300 (IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS).
12+
//!
13+
//! [`InstanceCreate`] and [`ExternalIpCreate`] are defined here for use
14+
//! by the `instance_create` endpoint in this version. These types don't
15+
//! have the `ip_version` field that was added in later versions.
16+
//!
17+
//! ## BGP Types
18+
//!
19+
//! Valid until 2025121200 (BGP_PEER_COLLISION_STATE).
20+
//!
21+
//! [`BgpPeerStatus`] and [`BgpPeerState`] are older versions without the
22+
//! `collision_state` field added in `BGP_PEER_COLLISION_STATE`.
623
724
use std::net::IpAddr;
825

26+
use nexus_types::external_api::params;
927
use omicron_common::api::external;
1028
use schemars::JsonSchema;
1129
use serde::{Deserialize, Serialize};
1230

31+
use crate::{v2025122300, v2026010100, v2026010300};
32+
1333
/// The current status of a BGP peer.
1434
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)]
1535
pub struct BgpPeerStatus {
@@ -59,3 +79,172 @@ pub enum BgpPeerState {
5979
/// messages with peers.
6080
Established,
6181
}
82+
83+
/// The type of IP address to attach to an instance during creation.
84+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
85+
#[serde(tag = "type", rename_all = "snake_case")]
86+
pub enum ExternalIpCreate {
87+
/// An IP address providing both inbound and outbound access.
88+
/// The address is automatically assigned from the provided IP pool
89+
/// or the default IP pool if not specified.
90+
Ephemeral {
91+
/// Name or ID of the IP pool to use. If unspecified, the
92+
/// default IP pool will be used.
93+
pool: Option<external::NameOrId>,
94+
},
95+
/// A floating IP address.
96+
Floating {
97+
/// The name or ID of the floating IP address to attach.
98+
floating_ip: external::NameOrId,
99+
},
100+
}
101+
102+
impl From<ExternalIpCreate> for v2026010300::ExternalIpCreate {
103+
fn from(old: ExternalIpCreate) -> v2026010300::ExternalIpCreate {
104+
match old {
105+
ExternalIpCreate::Ephemeral { pool } => {
106+
v2026010300::ExternalIpCreate::Ephemeral {
107+
pool,
108+
ip_version: None,
109+
}
110+
}
111+
ExternalIpCreate::Floating { floating_ip } => {
112+
v2026010300::ExternalIpCreate::Floating { floating_ip }
113+
}
114+
}
115+
}
116+
}
117+
118+
/// Create-time parameters for an `Instance`
119+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
120+
pub struct InstanceCreate {
121+
#[serde(flatten)]
122+
pub identity: external::IdentityMetadataCreateParams,
123+
/// The number of vCPUs to be allocated to the instance
124+
pub ncpus: external::InstanceCpuCount,
125+
/// The amount of RAM (in bytes) to be allocated to the instance
126+
pub memory: external::ByteCount,
127+
/// The hostname to be assigned to the instance
128+
pub hostname: external::Hostname,
129+
130+
/// User data for instance initialization systems (such as cloud-init).
131+
/// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and /
132+
/// characters with padding). Maximum 32 KiB unencoded data.
133+
#[serde(default, with = "params::UserData")]
134+
pub user_data: Vec<u8>,
135+
136+
/// The network interfaces to be created for this instance.
137+
#[serde(default)]
138+
pub network_interfaces: v2026010100::InstanceNetworkInterfaceAttachment,
139+
140+
/// The external IP addresses provided to this instance.
141+
///
142+
/// By default, all instances have outbound connectivity, but no inbound
143+
/// connectivity. These external addresses can be used to provide a fixed,
144+
/// known IP address for making inbound connections to the instance.
145+
#[serde(default)]
146+
pub external_ips: Vec<ExternalIpCreate>,
147+
148+
/// The multicast groups this instance should join.
149+
///
150+
/// The instance will be automatically added as a member of the specified
151+
/// multicast groups during creation, enabling it to send and receive
152+
/// multicast traffic for those groups.
153+
#[serde(default)]
154+
pub multicast_groups: Vec<external::NameOrId>,
155+
156+
/// A list of disks to be attached to the instance.
157+
///
158+
/// Disk attachments of type "create" will be created, while those of type
159+
/// "attach" must already exist.
160+
///
161+
/// The order of this list does not guarantee a boot order for the instance.
162+
/// Use the boot_disk attribute to specify a boot disk. When boot_disk is
163+
/// specified it will count against the disk attachment limit.
164+
#[serde(default)]
165+
pub disks: Vec<params::InstanceDiskAttachment>,
166+
167+
/// The disk the instance is configured to boot from.
168+
///
169+
/// This disk can either be attached if it already exists or created along
170+
/// with the instance.
171+
///
172+
/// Specifying a boot disk is optional but recommended to ensure predictable
173+
/// boot behavior. The boot disk can be set during instance creation or
174+
/// later if the instance is stopped. The boot disk counts against the disk
175+
/// attachment limit.
176+
///
177+
/// An instance that does not have a boot disk set will use the boot
178+
/// options specified in its UEFI settings, which are controlled by both the
179+
/// instance's UEFI firmware and the guest operating system. Boot options
180+
/// can change as disks are attached and detached, which may result in an
181+
/// instance that only boots to the EFI shell until a boot disk is set.
182+
#[serde(default)]
183+
pub boot_disk: Option<params::InstanceDiskAttachment>,
184+
185+
/// An allowlist of SSH public keys to be transferred to the instance via
186+
/// cloud-init during instance creation.
187+
///
188+
/// If not provided, all SSH public keys from the user's profile will be sent.
189+
/// If an empty list is provided, no public keys will be transmitted to the
190+
/// instance.
191+
pub ssh_public_keys: Option<Vec<external::NameOrId>>,
192+
193+
/// Should this instance be started upon creation; true by default.
194+
#[serde(default = "params::bool_true")]
195+
pub start: bool,
196+
197+
/// The auto-restart policy for this instance.
198+
///
199+
/// This policy determines whether the instance should be automatically
200+
/// restarted by the control plane on failure. If this is `null`, no
201+
/// auto-restart policy will be explicitly configured for this instance, and
202+
/// the control plane will select the default policy when determining
203+
/// whether the instance can be automatically restarted.
204+
///
205+
/// Currently, the global default auto-restart policy is "best-effort", so
206+
/// instances with `null` auto-restart policies will be automatically
207+
/// restarted. However, in the future, the default policy may be
208+
/// configurable through other mechanisms, such as on a per-project basis.
209+
/// In that case, any configured default policy will be used if this is
210+
/// `null`.
211+
#[serde(default)]
212+
pub auto_restart_policy: Option<external::InstanceAutoRestartPolicy>,
213+
214+
/// Anti-Affinity groups which this instance should be added.
215+
#[serde(default)]
216+
pub anti_affinity_groups: Vec<external::NameOrId>,
217+
218+
/// The CPU platform to be used for this instance. If this is `null`, the
219+
/// instance requires no particular CPU platform; when it is started the
220+
/// instance will have the most general CPU platform supported by the sled
221+
/// it is initially placed on.
222+
#[serde(default)]
223+
pub cpu_platform: Option<external::InstanceCpuPlatform>,
224+
}
225+
226+
impl From<InstanceCreate> for v2025122300::InstanceCreate {
227+
fn from(old: InstanceCreate) -> v2025122300::InstanceCreate {
228+
v2025122300::InstanceCreate {
229+
identity: old.identity,
230+
ncpus: old.ncpus,
231+
memory: old.memory,
232+
hostname: old.hostname,
233+
user_data: old.user_data,
234+
network_interfaces: old.network_interfaces,
235+
external_ips: old
236+
.external_ips
237+
.into_iter()
238+
.map(Into::into)
239+
.collect(),
240+
multicast_groups: old.multicast_groups,
241+
disks: old.disks,
242+
boot_disk: old.boot_disk,
243+
ssh_public_keys: old.ssh_public_keys,
244+
start: old.start,
245+
auto_restart_policy: old.auto_restart_policy,
246+
anti_affinity_groups: old.anti_affinity_groups,
247+
cpu_platform: old.cpu_platform,
248+
}
249+
}
250+
}

0 commit comments

Comments
 (0)