diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 1f2b04a0aef..d4f95c9be2a 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2843,159 +2843,6 @@ pub struct AddressLotBlock { pub last_address: IpAddr, } -/// A switch port settings identity whose id may be used to view additional -/// details. -#[derive( - ObjectIdentity, Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq, -)] -pub struct SwitchPortSettingsIdentity { - #[serde(flatten)] - pub identity: IdentityMetadata, -} - -/// 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. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct SwitchPortSettingsGroups { - /// The id of a port settings object referencing a port settings group. - pub port_settings_id: Uuid, - - /// The id of a port settings group being referenced by a port settings - /// object. - pub port_settings_group_id: Uuid, -} - -/// A port settings group is a named object that references a port settings -/// object. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct SwitchPortSettingsGroup { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// The port settings that comprise this group. - pub port_settings_id: Uuid, -} - -/// The link geometry associated with a switch port. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum SwitchPortGeometry { - /// The port contains a single QSFP28 link with four lanes. - Qsfp28x1, - - /// The port contains two QSFP28 links each with two lanes. - Qsfp28x2, - - /// The port contains four SFP28 links each with one lane. - Sfp28x4, -} - -/// A physical port configuration for a port settings object. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct SwitchPortConfig { - /// The id of the port settings object this configuration belongs to. - pub port_settings_id: Uuid, - - /// The physical link geometry of the port. - pub geometry: SwitchPortGeometry, -} - -/// The speed of a link. -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum LinkSpeed { - /// Zero gigabits per second. - Speed0G, - /// 1 gigabit per second. - Speed1G, - /// 10 gigabits per second. - Speed10G, - /// 25 gigabits per second. - Speed25G, - /// 40 gigabits per second. - Speed40G, - /// 50 gigabits per second. - Speed50G, - /// 100 gigabits per second. - Speed100G, - /// 200 gigabits per second. - Speed200G, - /// 400 gigabits per second. - Speed400G, -} - -/// The forward error correction mode of a link. -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum LinkFec { - /// Firecode forward error correction. - Firecode, - /// No forward error correction. - None, - /// Reed-Solomon forward error correction. - Rs, -} - -/// A link configuration for a port settings object. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct SwitchPortLinkConfig { - /// The port settings this link configuration belongs to. - pub port_settings_id: Uuid, - - /// The name of this link. - pub link_name: Name, - - /// The maximum transmission unit for this link. - pub mtu: u16, - - /// The requested forward-error correction method. If this is not - /// specified, the standard FEC for the underlying media will be applied - /// if it can be determined. - pub fec: Option, - - /// The configured speed of the link. - pub speed: LinkSpeed, - - /// Whether or not the link has autonegotiation enabled. - pub autoneg: bool, - - /// The link-layer discovery protocol service configuration for this - /// link. - pub lldp_link_config: Option, - - /// The tx_eq configuration for this link. - pub tx_eq_config: Option, -} - -/// A link layer discovery protocol (LLDP) service configuration. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct LldpLinkConfig { - /// The id of this LLDP service instance. - pub id: Uuid, - - /// Whether or not the LLDP service is enabled. - pub enabled: bool, - - /// The LLDP link name TLV. - pub link_name: Option, - - /// The LLDP link description TLV. - pub link_description: Option, - - /// The LLDP chassis identifier TLV. - pub chassis_id: Option, - - /// The LLDP system name TLV. - pub system_name: Option, - - /// The LLDP system description TLV. - pub system_description: Option, - - /// The LLDP management IP TLV. - pub management_ip: Option, -} - /// Information about LLDP advertisements from other network entities directly /// connected to a switch port. This structure contains both metadata about /// when and where the neighbor was seen, as well as the specific information @@ -3040,72 +2887,6 @@ impl SimpleIdentity for LldpNeighbor { } } -/// Per-port tx-eq overrides. This can be used to fine-tune the transceiver -/// equalization settings to improve signal integrity. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct TxEqConfig { - /// Pre-cursor tap1 - pub pre1: Option, - /// Pre-cursor tap2 - pub pre2: Option, - /// Main tap - pub main: Option, - /// Post-cursor tap2 - pub post2: Option, - /// Post-cursor tap1 - pub post1: Option, -} - -/// Describes the kind of an switch interface. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum SwitchInterfaceKind { - /// Primary interfaces are associated with physical links. There is exactly - /// one primary interface per physical link. - Primary, - - /// VLAN interfaces allow physical interfaces to be multiplexed onto - /// multiple logical links, each distinguished by a 12-bit 802.1Q Ethernet - /// tag. - Vlan, - - /// Loopback interfaces are anchors for IP addresses that are not specific - /// to any particular port. - Loopback, -} - -/// A switch port interface configuration for a port settings object. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct SwitchInterfaceConfig { - /// The port settings object this switch interface configuration belongs to. - pub port_settings_id: Uuid, - - /// A unique identifier for this switch interface. - pub id: Uuid, - - /// The name of this switch interface. - pub interface_name: Name, - - /// Whether or not IPv6 is enabled on this interface. - pub v6_enabled: bool, - - /// The switch interface kind. - pub kind: SwitchInterfaceKind, -} - -/// A switch port VLAN interface configuration for a port settings object. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct SwitchVlanInterfaceConfig { - /// The switch interface configuration this VLAN interface configuration - /// belongs to. - pub interface_config_id: Uuid, - - /// 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. - pub vlan_id: u16, -} - /// A route configuration for a port settings object. #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] pub struct SwitchPortRouteConfig { diff --git a/nexus/db-model/src/switch_interface.rs b/nexus/db-model/src/switch_interface.rs index 0bf5e54d141..e713b07c8f5 100644 --- a/nexus/db-model/src/switch_interface.rs +++ b/nexus/db-model/src/switch_interface.rs @@ -10,7 +10,6 @@ use ipnetwork::IpNetwork; use nexus_db_schema::schema::{loopback_address, switch_vlan_interface_config}; use nexus_types::external_api::networking as networking_types; use nexus_types::identity::Asset; -use omicron_common::api::external; use omicron_uuid_kinds::LoopbackAddressKind; use omicron_uuid_kinds::TypedUuid; use serde::{Deserialize, Serialize}; @@ -53,20 +52,6 @@ impl From for DbSwitchInterfaceKind { } } -impl Into for DbSwitchInterfaceKind { - fn into(self) -> external::SwitchInterfaceKind { - match self { - DbSwitchInterfaceKind::Primary => { - external::SwitchInterfaceKind::Primary - } - DbSwitchInterfaceKind::Vlan => external::SwitchInterfaceKind::Vlan, - DbSwitchInterfaceKind::Loopback => { - external::SwitchInterfaceKind::Loopback - } - } - } -} - #[derive( Queryable, Insertable, @@ -89,9 +74,11 @@ impl SwitchVlanInterfaceConfig { } } -impl Into for SwitchVlanInterfaceConfig { - fn into(self) -> external::SwitchVlanInterfaceConfig { - external::SwitchVlanInterfaceConfig { +impl Into + for SwitchVlanInterfaceConfig +{ + fn into(self) -> networking_types::SwitchVlanInterfaceConfig { + networking_types::SwitchVlanInterfaceConfig { interface_config_id: self.interface_config_id, vlan_id: self.vid.into(), } diff --git a/nexus/db-model/src/switch_port.rs b/nexus/db-model/src/switch_port.rs index dc35dad575a..057fc7bd497 100644 --- a/nexus/db-model/src/switch_port.rs +++ b/nexus/db-model/src/switch_port.rs @@ -130,22 +130,22 @@ impl From for PortFec { } } -impl From for SwitchLinkFec { - fn from(value: external::LinkFec) -> Self { +impl From for SwitchLinkFec { + fn from(value: networking_types::LinkFec) -> Self { match value { - external::LinkFec::Firecode => SwitchLinkFec::Firecode, - external::LinkFec::None => SwitchLinkFec::None, - external::LinkFec::Rs => SwitchLinkFec::Rs, + networking_types::LinkFec::Firecode => SwitchLinkFec::Firecode, + networking_types::LinkFec::None => SwitchLinkFec::None, + networking_types::LinkFec::Rs => SwitchLinkFec::Rs, } } } -impl From for external::LinkFec { +impl From for networking_types::LinkFec { fn from(value: SwitchLinkFec) -> Self { match value { - SwitchLinkFec::Firecode => external::LinkFec::Firecode, - SwitchLinkFec::None => external::LinkFec::None, - SwitchLinkFec::Rs => external::LinkFec::Rs, + SwitchLinkFec::Firecode => networking_types::LinkFec::Firecode, + SwitchLinkFec::None => networking_types::LinkFec::None, + SwitchLinkFec::Rs => networking_types::LinkFec::Rs, } } } @@ -166,34 +166,34 @@ impl From for PortSpeed { } } -impl From for SwitchLinkSpeed { - fn from(value: external::LinkSpeed) -> Self { +impl From for SwitchLinkSpeed { + fn from(value: networking_types::LinkSpeed) -> Self { match value { - external::LinkSpeed::Speed0G => SwitchLinkSpeed::Speed0G, - external::LinkSpeed::Speed1G => SwitchLinkSpeed::Speed1G, - external::LinkSpeed::Speed10G => SwitchLinkSpeed::Speed10G, - external::LinkSpeed::Speed25G => SwitchLinkSpeed::Speed25G, - external::LinkSpeed::Speed40G => SwitchLinkSpeed::Speed40G, - external::LinkSpeed::Speed50G => SwitchLinkSpeed::Speed50G, - external::LinkSpeed::Speed100G => SwitchLinkSpeed::Speed100G, - external::LinkSpeed::Speed200G => SwitchLinkSpeed::Speed200G, - external::LinkSpeed::Speed400G => SwitchLinkSpeed::Speed400G, + networking_types::LinkSpeed::Speed0G => Self::Speed0G, + networking_types::LinkSpeed::Speed1G => Self::Speed1G, + networking_types::LinkSpeed::Speed10G => Self::Speed10G, + networking_types::LinkSpeed::Speed25G => Self::Speed25G, + networking_types::LinkSpeed::Speed40G => Self::Speed40G, + networking_types::LinkSpeed::Speed50G => Self::Speed50G, + networking_types::LinkSpeed::Speed100G => Self::Speed100G, + networking_types::LinkSpeed::Speed200G => Self::Speed200G, + networking_types::LinkSpeed::Speed400G => Self::Speed400G, } } } -impl From for external::LinkSpeed { +impl From for networking_types::LinkSpeed { fn from(value: SwitchLinkSpeed) -> Self { match value { - SwitchLinkSpeed::Speed0G => external::LinkSpeed::Speed0G, - SwitchLinkSpeed::Speed1G => external::LinkSpeed::Speed1G, - SwitchLinkSpeed::Speed10G => external::LinkSpeed::Speed10G, - SwitchLinkSpeed::Speed25G => external::LinkSpeed::Speed25G, - SwitchLinkSpeed::Speed40G => external::LinkSpeed::Speed40G, - SwitchLinkSpeed::Speed50G => external::LinkSpeed::Speed50G, - SwitchLinkSpeed::Speed100G => external::LinkSpeed::Speed100G, - SwitchLinkSpeed::Speed200G => external::LinkSpeed::Speed200G, - SwitchLinkSpeed::Speed400G => external::LinkSpeed::Speed400G, + SwitchLinkSpeed::Speed0G => Self::Speed0G, + SwitchLinkSpeed::Speed1G => Self::Speed1G, + SwitchLinkSpeed::Speed10G => Self::Speed10G, + SwitchLinkSpeed::Speed25G => Self::Speed25G, + SwitchLinkSpeed::Speed40G => Self::Speed40G, + SwitchLinkSpeed::Speed50G => Self::Speed50G, + SwitchLinkSpeed::Speed100G => Self::Speed100G, + SwitchLinkSpeed::Speed200G => Self::Speed200G, + SwitchLinkSpeed::Speed400G => Self::Speed400G, } } } @@ -214,17 +214,17 @@ impl From for SwitchPortGeometry { } } -impl Into for SwitchPortGeometry { - fn into(self) -> external::SwitchPortGeometry { +impl Into for SwitchPortGeometry { + fn into(self) -> networking_types::SwitchPortGeometry { match self { SwitchPortGeometry::Qsfp28x1 => { - external::SwitchPortGeometry::Qsfp28x1 + networking_types::SwitchPortGeometry::Qsfp28x1 } SwitchPortGeometry::Qsfp28x2 => { - external::SwitchPortGeometry::Qsfp28x2 + networking_types::SwitchPortGeometry::Qsfp28x2 } SwitchPortGeometry::Sfp28x4 => { - external::SwitchPortGeometry::Sfp28x4 + networking_types::SwitchPortGeometry::Sfp28x4 } } } @@ -354,9 +354,11 @@ impl SwitchPortSettings { } } -impl Into for SwitchPortSettings { - fn into(self) -> external::SwitchPortSettingsIdentity { - external::SwitchPortSettingsIdentity { identity: self.identity() } +impl Into for SwitchPortSettings { + fn into(self) -> networking_types::SwitchPortSettingsIdentity { + networking_types::SwitchPortSettingsIdentity { + identity: self.identity(), + } } } @@ -369,9 +371,11 @@ pub struct SwitchPortSettingsGroups { pub port_settings_group_id: Uuid, } -impl Into for SwitchPortSettingsGroups { - fn into(self) -> external::SwitchPortSettingsGroups { - external::SwitchPortSettingsGroups { +impl Into + for SwitchPortSettingsGroups +{ + fn into(self) -> networking_types::SwitchPortSettingsGroups { + networking_types::SwitchPortSettingsGroups { port_settings_id: self.port_settings_id, port_settings_group_id: self.port_settings_group_id, } @@ -395,9 +399,11 @@ pub struct SwitchPortSettingsGroup { pub port_settings_id: Uuid, } -impl Into for SwitchPortSettingsGroup { - fn into(self) -> external::SwitchPortSettingsGroup { - external::SwitchPortSettingsGroup { +impl Into + for SwitchPortSettingsGroup +{ + fn into(self) -> networking_types::SwitchPortSettingsGroup { + networking_types::SwitchPortSettingsGroup { identity: self.identity(), port_settings_id: self.port_settings_id, } @@ -419,9 +425,9 @@ impl SwitchPortConfig { } } -impl Into for SwitchPortConfig { - fn into(self) -> external::SwitchPortConfig { - external::SwitchPortConfig { +impl Into for SwitchPortConfig { + fn into(self) -> networking_types::SwitchPortConfig { + networking_types::SwitchPortConfig { port_settings_id: self.port_settings_id, geometry: self.geometry.into(), } @@ -529,9 +535,9 @@ impl LldpLinkConfig { // This converts the internal database version of the config into the // user-facing version. -impl Into for LldpLinkConfig { - fn into(self) -> external::LldpLinkConfig { - external::LldpLinkConfig { +impl Into for LldpLinkConfig { + fn into(self) -> networking_types::LldpLinkConfig { + networking_types::LldpLinkConfig { id: self.id, enabled: self.enabled, link_name: self.link_name.clone(), @@ -578,9 +584,9 @@ impl TxEqConfig { // This converts the internal database version of the config into the // user-facing version. -impl Into for TxEqConfig { - fn into(self) -> external::TxEqConfig { - external::TxEqConfig { +impl Into for TxEqConfig { + fn into(self) -> sled_agent_types::early_networking::TxEqConfig { + sled_agent_types::early_networking::TxEqConfig { pre1: self.pre1, pre2: self.pre2, main: self.main, @@ -626,18 +632,6 @@ impl SwitchInterfaceConfig { } } -impl Into for SwitchInterfaceConfig { - fn into(self) -> external::SwitchInterfaceConfig { - external::SwitchInterfaceConfig { - port_settings_id: self.port_settings_id, - id: self.id, - interface_name: self.interface_name.into(), - v6_enabled: self.v6_enabled, - kind: self.kind.into(), - } - } -} - #[derive( Queryable, Insertable, diff --git a/nexus/db-queries/src/db/datastore/lldp.rs b/nexus/db-queries/src/db/datastore/lldp.rs index bae86040dd7..48f46e5f652 100644 --- a/nexus/db-queries/src/db/datastore/lldp.rs +++ b/nexus/db-queries/src/db/datastore/lldp.rs @@ -16,6 +16,7 @@ use ipnetwork::IpNetwork; use nexus_db_errors::ErrorHandler; use nexus_db_errors::public_error_from_diesel; use nexus_db_model::DbSwitchSlot; +use nexus_types::external_api::networking as networking_types; use omicron_common::api::external; use omicron_common::api::external::Error; use omicron_common::api::external::LookupResult; @@ -105,7 +106,7 @@ impl DataStore { rack_id: Uuid, switch_slot: SwitchSlot, port_name: Name, - ) -> LookupResult { + ) -> LookupResult { use nexus_db_schema::schema::lldp_link_config; use nexus_db_schema::schema::lldp_link_config::dsl; @@ -145,7 +146,7 @@ impl DataStore { rack_id: Uuid, switch_slot: SwitchSlot, port_name: Name, - config: external::LldpLinkConfig, + config: networking_types::LldpLinkConfig, ) -> UpdateResult<()> { use nexus_db_schema::schema::lldp_link_config::dsl; diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 8bf25eba10f..9f68ebc5510 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -27,7 +27,7 @@ use nexus_db_errors::ErrorHandler; use nexus_db_errors::OptionalError; use nexus_db_errors::public_error_from_diesel; use nexus_db_model::{ - AddressLot, BgpConfig, DbSwitchSlot, SqlU16, + AddressLot, BgpConfig, DbSwitchInterfaceKind, DbSwitchSlot, SqlU16, SwitchPortBgpPeerConfigAllowExport, SwitchPortBgpPeerConfigAllowImport, SwitchPortBgpPeerConfigCommunity, }; @@ -187,23 +187,80 @@ impl SwitchPortSettingsCombinedResult { } } -impl Into for SwitchPortSettingsCombinedResult { - fn into(self) -> networking::SwitchPortSettings { - networking::SwitchPortSettings { - identity: self.settings.identity(), - port: self.port.into(), - groups: self.groups.into_iter().map(Into::into).collect(), - links: self.links.into_iter().map(Into::into).collect(), - interfaces: self.interfaces.into_iter().map(Into::into).collect(), - vlan_interfaces: self - .vlan_interfaces - .into_iter() - .map(Into::into) - .collect(), - routes: self.routes.into_iter().map(Into::into).collect(), - bgp_peers: self.bgp_peers.into_iter().map(Into::into).collect(), - addresses: self.addresses, +impl TryFrom + for networking::SwitchPortSettings +{ + type Error = Error; + + fn try_from( + result: SwitchPortSettingsCombinedResult, + ) -> Result { + // We expect the db to hold exactly one `vlan_interface` row for any + // `interface` that has kind `Vlan`. Build up a map of all the vlan + // interface rows; we'll then prune through that map as we loop over all + // the interfaces and check that it's empty when we're done. + // + // We expect to find exactly 1 entry for every `Vlan` interface and + // nothing extra; anything else is some kind of internal inconsistency. + let mut vlan_by_interface = BTreeMap::new(); + for vlan_iface in result.vlan_interfaces { + let SwitchVlanInterfaceConfig { interface_config_id, vid } = + vlan_iface; + vlan_by_interface.insert(interface_config_id, vid); } + + let mut interfaces = Vec::with_capacity(result.interfaces.len()); + for iface in result.interfaces { + let kind = match iface.kind { + DbSwitchInterfaceKind::Primary => { + networking::SwitchInterfaceKind::Primary + } + DbSwitchInterfaceKind::Loopback => { + networking::SwitchInterfaceKind::Loopback + } + DbSwitchInterfaceKind::Vlan => { + let Some(vid) = vlan_by_interface.remove(&iface.id) else { + return Err(external::Error::internal_error(format!( + "switch port settings has inconsistent data: \ + interface {iface:?} has type vlan, but \ + vlan_interfaces does not have matching interface: \ + {vlan_by_interface:?}" + ))); + }; + networking::SwitchInterfaceKind::Vlan( + networking::SwitchVlanInterface { vid: *vid }, + ) + } + }; + + interfaces.push(networking::SwitchInterfaceConfig { + port_settings_id: iface.port_settings_id, + id: iface.id, + interface_name: iface.interface_name.into(), + v6_enabled: iface.v6_enabled, + kind, + }); + } + + if !vlan_by_interface.is_empty() { + return Err(external::Error::internal_error(format!( + "switch port settings has inconsistent data: \ + `vlan_interfaces` references interface IDs that do not have \ + a matching vlan interface: leftover vlan values: \ + {vlan_by_interface:?}, assembled interfaces: {interfaces:?}" + ))); + } + + Ok(networking::SwitchPortSettings { + identity: result.settings.identity(), + port: result.port.into(), + groups: result.groups.into_iter().map(Into::into).collect(), + links: result.links.into_iter().map(Into::into).collect(), + interfaces, + routes: result.routes.into_iter().map(Into::into).collect(), + bgp_peers: result.bgp_peers.into_iter().map(Into::into).collect(), + addresses: result.addresses, + }) } } @@ -219,7 +276,7 @@ pub struct LinkConfigCombinedResult { pub tx_eq_config: Option, } -impl From for external::SwitchPortLinkConfig { +impl From for networking::SwitchPortLinkConfig { fn from(value: LinkConfigCombinedResult) -> Self { Self { port_settings_id: value.port_settings_id, diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index 13c0da9ac25..b877b0118d0 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -33,6 +33,7 @@ use nexus_types_versions::v2026_01_16_01; use nexus_types_versions::v2026_01_22_00; use nexus_types_versions::v2026_01_30_01; use nexus_types_versions::v2026_02_13_01; +use nexus_types_versions::v2026_04_16_00; use omicron_common::address::IpRange; use omicron_common::api::external::{ http_pagination::{ @@ -81,6 +82,7 @@ api_versions!([ // | date-based version should be at the top of the list. // v // (next_yyyy_mm_dd_nn, IDENT), + (2026_04_29_01, REMOVE_DUPLICATED_NETWORKING_TYPES), (2026_04_29_00, METRICS_ADD_JOULES), (2026_04_24_00, DROPSHOT_WEBSOCKET_SPEC_CHANGE), (2026_04_19_00, INLINE_ROUTER_PEER_IP_ADDR), @@ -4583,7 +4585,7 @@ pub trait NexusExternalApi { method = POST, path = "/v1/system/networking/switch-port-settings", tags = ["system/networking"], - versions = VERSION_STRONGER_BGP_UNNUMBERED_TYPES.., + versions = VERSION_REMOVE_DUPLICATED_NETWORKING_TYPES.., }] async fn networking_switch_port_settings_create( rqctx: RequestContext, @@ -4593,6 +4595,35 @@ pub trait NexusExternalApi { HttpError, >; + #[endpoint { + operation_id = "networking_switch_port_settings_create", + method = POST, + path = "/v1/system/networking/switch-port-settings", + tags = ["system/networking"], + versions = VERSION_STRONGER_BGP_UNNUMBERED_TYPES..VERSION_REMOVE_DUPLICATED_NETWORKING_TYPES, + }] + async fn networking_switch_port_settings_create_v2026_04_16_00( + rqctx: RequestContext, + new_settings: TypedBody< + v2026_04_16_00::networking::SwitchPortSettingsCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { + Self::networking_switch_port_settings_create( + rqctx, + new_settings.try_map(TryFrom::try_from).map_err(|err| { + HttpError::for_bad_request( + None, + InlineErrorChain::new(&err).to_string(), + ) + })?, + ) + .await + .map(|response| response.map(From::from)) + } + #[endpoint { operation_id = "networking_switch_port_settings_create", method = POST, @@ -4609,7 +4640,7 @@ pub trait NexusExternalApi { HttpResponseCreated, HttpError, > { - Self::networking_switch_port_settings_create( + Self::networking_switch_port_settings_create_v2026_04_16_00( rqctx, new_settings.try_map(TryFrom::try_from).map_err(|err| { HttpError::for_bad_request( @@ -4675,7 +4706,9 @@ pub trait NexusExternalApi { PaginatedByNameOrId, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk< + ResultsPage, + >, HttpError, >; @@ -4684,7 +4717,7 @@ pub trait NexusExternalApi { method = GET, path = "/v1/system/networking/switch-port-settings/{port}", tags = ["system/networking"], - versions = VERSION_STRONGER_BGP_UNNUMBERED_TYPES.., + versions = VERSION_REMOVE_DUPLICATED_NETWORKING_TYPES.., }] async fn networking_switch_port_settings_view( rqctx: RequestContext, @@ -4696,13 +4729,13 @@ pub trait NexusExternalApi { method = GET, path = "/v1/system/networking/switch-port-settings/{port}", tags = ["system/networking"], - versions = VERSION_BGP_UNNUMBERED_PEERS..VERSION_STRONGER_BGP_UNNUMBERED_TYPES, + versions = VERSION_STRONGER_BGP_UNNUMBERED_TYPES..VERSION_REMOVE_DUPLICATED_NETWORKING_TYPES, }] - async fn networking_switch_port_settings_view_v2026_02_13_01( + async fn networking_switch_port_settings_view_v2026_04_16_00( rqctx: RequestContext, path_params: Path, ) -> Result< - HttpResponseOk, + HttpResponseOk, HttpError, > { Self::networking_switch_port_settings_view(rqctx, path_params) @@ -4710,6 +4743,28 @@ pub trait NexusExternalApi { .map(|response| response.map(From::from)) } + #[endpoint { + operation_id = "networking_switch_port_settings_view", + method = GET, + path = "/v1/system/networking/switch-port-settings/{port}", + tags = ["system/networking"], + versions = VERSION_BGP_UNNUMBERED_PEERS..VERSION_STRONGER_BGP_UNNUMBERED_TYPES, + }] + async fn networking_switch_port_settings_view_v2026_02_13_01( + rqctx: RequestContext, + path_params: Path, + ) -> Result< + HttpResponseOk, + HttpError, + > { + Self::networking_switch_port_settings_view_v2026_04_16_00( + rqctx, + path_params, + ) + .await + .map(|response| response.map(From::from)) + } + /// Get information about switch port (old version with required BgpPeer.addr) #[endpoint { operation_id = "networking_switch_port_settings_view", @@ -4898,7 +4953,7 @@ pub trait NexusExternalApi { rqctx: RequestContext, path_params: Path, query_params: Query, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /// Fetch LLDP configuration for switch port #[endpoint { @@ -4912,7 +4967,8 @@ pub trait NexusExternalApi { rqctx: RequestContext, path_params: Path, query_params: Query, - ) -> Result, HttpError> { + ) -> Result, HttpError> + { let query_params = query_params.try_map(TryInto::try_into)?; Self::networking_switch_port_lldp_config_view( rqctx, @@ -4933,7 +4989,7 @@ pub trait NexusExternalApi { rqctx: RequestContext, path_params: Path, query_params: Query, - config: TypedBody, + config: TypedBody, ) -> Result; /// Update LLDP configuration for switch port @@ -4948,7 +5004,7 @@ pub trait NexusExternalApi { rqctx: RequestContext, path_params: Path, query_params: Query, - config: TypedBody, + config: TypedBody, ) -> Result { let query_params = query_params.try_map(TryInto::try_into)?; Self::networking_switch_port_lldp_config_update( diff --git a/nexus/src/app/lldp.rs b/nexus/src/app/lldp.rs index 2438828d71b..8ddb0f3ec07 100644 --- a/nexus/src/app/lldp.rs +++ b/nexus/src/app/lldp.rs @@ -10,8 +10,8 @@ use lldpd_client::types::ChassisId; use lldpd_client::types::Neighbor; use lldpd_client::types::PortId; use nexus_db_queries::context::OpContext; +use nexus_types::external_api::networking::LldpLinkConfig; use omicron_common::api::external::Error; -use omicron_common::api::external::LldpLinkConfig; use omicron_common::api::external::LldpNeighbor; use omicron_common::api::external::LookupResult; use omicron_common::api::external::Name; diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index fe8a5ca4321..6823acb5c37 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -83,14 +83,12 @@ use omicron_common::api::external::Error; use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceNetworkInterface; use omicron_common::api::external::InternalContext; -use omicron_common::api::external::LldpLinkConfig; use omicron_common::api::external::LldpNeighbor; 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::ServiceIcmpConfig; -use omicron_common::api::external::SwitchPortSettingsIdentity; use omicron_common::api::external::VpcFirewallRuleUpdateParams; use omicron_common::api::external::VpcFirewallRules; use omicron_common::api::external::http_pagination::PaginatedBy; @@ -4072,7 +4070,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let params = new_settings.into_inner(); let result = nexus.switch_port_settings_post(&opctx, params).await?; - let settings: networking::SwitchPortSettings = result.into(); + let settings: networking::SwitchPortSettings = result.try_into()?; Ok(HttpResponseCreated(settings)) }) .await @@ -4096,7 +4094,7 @@ impl NexusExternalApi for NexusExternalApiImpl { PaginatedByNameOrId, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { let apictx = rqctx.context(); @@ -4140,7 +4138,7 @@ impl NexusExternalApi for NexusExternalApiImpl { crate::context::op_context_for_external_api(&rqctx).await?; let settings = nexus.switch_port_settings_get(&opctx, &query).await?; - Ok(HttpResponseOk(settings.into())) + Ok(HttpResponseOk(settings.try_into()?)) }; apictx .context @@ -4242,7 +4240,7 @@ impl NexusExternalApi for NexusExternalApiImpl { rqctx: RequestContext, path_params: Path, query_params: Query, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4271,7 +4269,7 @@ impl NexusExternalApi for NexusExternalApiImpl { rqctx: RequestContext, path_params: Path, query_params: Query, - config: TypedBody, + config: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index 87493d84c0f..ff54799b152 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -11,15 +11,15 @@ use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::networking::{ Address, AddressConfig, AddressLotBlockCreate, AddressLotCreate, BgpAnnounceSetCreate, BgpAnnouncementCreate, BgpConfigCreate, BgpPeer, - BgpPeerConfig, LinkConfigCreate, LldpLinkConfigCreate, Route, RouteConfig, - SwitchInterfaceConfigCreate, SwitchInterfaceKind, SwitchPort, - SwitchPortApplySettings, SwitchPortSettings, SwitchPortSettingsCreate, + BgpPeerConfig, LinkConfigCreate, LinkFec, LinkSpeed, LldpLinkConfigCreate, + Route, RouteConfig, SwitchInterfaceConfigCreate, SwitchInterfaceKind, + SwitchPort, SwitchPortApplySettings, SwitchPortSettings, + SwitchPortSettingsCreate, }; use nexus_types::external_api::rack::Rack; use omicron_common::api::external::Name; use omicron_common::api::external::{ - self, AddressLotKind, IdentityMetadataCreateParams, LinkFec, LinkSpeed, - NameOrId, + AddressLotKind, IdentityMetadataCreateParams, NameOrId, }; use oxnet::IpNet; use sled_agent_types::early_networking::{ImportExportPolicy, RouterPeerType}; @@ -230,7 +230,7 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { let ifx0 = &created.interfaces[0]; assert_eq!(&ifx0.interface_name.to_string(), "phy0"); assert_eq!(ifx0.v6_enabled, true); - assert_eq!(ifx0.kind, external::SwitchInterfaceKind::Primary); + assert_eq!(ifx0.kind, SwitchInterfaceKind::Primary); let route0 = &created.routes[0]; assert_eq!(route0.dst, IpNet::from_str("1.2.3.0/24").unwrap()); @@ -272,7 +272,7 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { let ifx0 = &roundtrip.interfaces[0]; assert_eq!(&ifx0.interface_name.to_string(), "phy0"); assert_eq!(ifx0.v6_enabled, true); - assert_eq!(ifx0.kind, external::SwitchInterfaceKind::Primary); + assert_eq!(ifx0.kind, SwitchInterfaceKind::Primary); let route0 = &roundtrip.routes[0]; assert_eq!(route0.dst, IpNet::from_str("1.2.3.0/24").unwrap()); diff --git a/nexus/types/versions/src/bgp_unnumbered_peers/networking.rs b/nexus/types/versions/src/bgp_unnumbered_peers/networking.rs index c63518a455a..1a5bfd02c53 100644 --- a/nexus/types/versions/src/bgp_unnumbered_peers/networking.rs +++ b/nexus/types/versions/src/bgp_unnumbered_peers/networking.rs @@ -16,6 +16,11 @@ //! - `SwitchPortSettings` updated to use the new `BgpPeer` //! - `SwitchPortSettingsCreate` updated to use the new `BgpPeerConfig`. +use crate::v2025_11_20_00::networking::SwitchInterfaceConfig; +use crate::v2025_11_20_00::networking::SwitchPortConfig; +use crate::v2025_11_20_00::networking::SwitchPortLinkConfig; +use crate::v2025_11_20_00::networking::SwitchPortSettingsGroups; +use crate::v2025_11_20_00::networking::SwitchVlanInterfaceConfig; use crate::v2025_12_12_00::networking::BgpPeerState; use api_identity::ObjectIdentity; use omicron_common::api::external::{ @@ -314,19 +319,19 @@ pub struct SwitchPortSettings { pub identity: IdentityMetadata, /// Switch port settings included from other switch port settings groups. - pub groups: Vec, + pub groups: Vec, /// Layer 1 physical port settings. - pub port: external::SwitchPortConfig, + pub port: SwitchPortConfig, /// Layer 2 link settings. - pub links: Vec, + pub links: Vec, /// Layer 3 interface settings. - pub interfaces: Vec, + pub interfaces: Vec, /// Vlan interface settings. - pub vlan_interfaces: Vec, + pub vlan_interfaces: Vec, /// IP route settings. pub routes: Vec, diff --git a/nexus/types/versions/src/impls/networking.rs b/nexus/types/versions/src/impls/networking.rs index 0831b35c920..62db2a208ff 100644 --- a/nexus/types/versions/src/impls/networking.rs +++ b/nexus/types/versions/src/impls/networking.rs @@ -5,6 +5,8 @@ use crate::latest; use omicron_common::api::external::IdentityMetadataCreateParams; use oxnet::IpNet; +use sled_agent_types_versions::latest::early_networking::PortFec; +use sled_agent_types_versions::latest::early_networking::PortSpeed; impl From for latest::networking::AddressLotBlockCreate { fn from(ipnet: IpNet) -> Self { @@ -69,3 +71,32 @@ impl latest::networking::SwitchPortSettingsCreate { } } } + +// TODO-cleanup We could push `LinkFec` and `LinkSpeed` down into +// sled-agent-types-versions and re-export them instead of having these +// conversions. That requires . +impl From for latest::networking::LinkFec { + fn from(x: PortFec) -> Self { + match x { + PortFec::Firecode => Self::Firecode, + PortFec::None => Self::None, + PortFec::Rs => Self::Rs, + } + } +} + +impl From for latest::networking::LinkSpeed { + fn from(x: PortSpeed) -> Self { + match x { + PortSpeed::Speed0G => Self::Speed0G, + PortSpeed::Speed1G => Self::Speed1G, + PortSpeed::Speed10G => Self::Speed10G, + PortSpeed::Speed25G => Self::Speed25G, + PortSpeed::Speed40G => Self::Speed40G, + PortSpeed::Speed50G => Self::Speed50G, + PortSpeed::Speed100G => Self::Speed100G, + PortSpeed::Speed200G => Self::Speed200G, + PortSpeed::Speed400G => Self::Speed400G, + } + } +} diff --git a/nexus/types/versions/src/initial/networking.rs b/nexus/types/versions/src/initial/networking.rs index 6e249d36be3..a8758778559 100644 --- a/nexus/types/versions/src/initial/networking.rs +++ b/nexus/types/versions/src/initial/networking.rs @@ -10,8 +10,8 @@ use api_identity::ObjectIdentity; use omicron_common::api::external; use omicron_common::api::external::{ - AddressLotKind, IdentityMetadata, IdentityMetadataCreateParams, LinkFec, - LinkSpeed, Name, NameOrId, ObjectIdentity, + AddressLotKind, IdentityMetadata, IdentityMetadataCreateParams, Name, + NameOrId, ObjectIdentity, }; use oxnet::IpNet; use schemars::JsonSchema; @@ -140,6 +140,40 @@ pub struct SwitchPort { pub port_settings_id: Option, } +/// A switch port settings identity whose id may be used to view additional +/// details. +#[derive( + ObjectIdentity, Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq, +)] +pub struct SwitchPortSettingsIdentity { + #[serde(flatten)] + pub identity: IdentityMetadata, +} + +/// 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. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct SwitchPortSettingsGroups { + /// The id of a port settings object referencing a port settings group. + pub port_settings_id: Uuid, + + /// The id of a port settings group being referenced by a port settings + /// object. + pub port_settings_group_id: Uuid, +} + +/// A port settings group is a named object that references a port settings +/// object. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct SwitchPortSettingsGroup { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// The port settings that comprise this group. + pub port_settings_id: Uuid, +} + /// Parameters for creating a port settings group. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct SwtichPortSettingsGroupCreate { @@ -149,6 +183,16 @@ pub struct SwtichPortSettingsGroupCreate { pub settings: SwitchPortSettingsCreate, } +/// A physical port configuration for a port settings object. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct SwitchPortConfig { + /// The id of the port settings object this configuration belongs to. + pub port_settings_id: Uuid, + + /// The physical link geometry of the port. + pub geometry: SwitchPortGeometry, +} + /// 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 @@ -208,7 +252,7 @@ pub struct SwitchPortConfigCreate { } /// The link geometry associated with a switch port. -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] #[serde(rename_all = "snake_case")] pub enum SwitchPortGeometry { /// The port contains a single QSFP28 link with four lanes. @@ -275,9 +319,35 @@ pub struct LldpLinkConfigCreate { pub management_ip: Option, } -impl PartialEq - for omicron_common::api::external::LldpLinkConfig -{ +/// A link layer discovery protocol (LLDP) service configuration. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct LldpLinkConfig { + /// The id of this LLDP service instance. + pub id: Uuid, + + /// Whether or not the LLDP service is enabled. + pub enabled: bool, + + /// The LLDP link name TLV. + pub link_name: Option, + + /// The LLDP link description TLV. + pub link_description: Option, + + /// The LLDP chassis identifier TLV. + pub chassis_id: Option, + + /// The LLDP system name TLV. + pub system_name: Option, + + /// The LLDP system description TLV. + pub system_description: Option, + + /// The LLDP management IP TLV. + pub management_ip: Option, +} + +impl PartialEq for LldpLinkConfig { fn eq(&self, other: &LldpLinkConfigCreate) -> bool { self.enabled == other.enabled && self.link_name == other.link_name @@ -289,13 +359,8 @@ impl PartialEq } } -impl PartialEq - for LldpLinkConfigCreate -{ - fn eq( - &self, - other: &omicron_common::api::external::LldpLinkConfig, - ) -> bool { +impl PartialEq for LldpLinkConfigCreate { + fn eq(&self, other: &LldpLinkConfig) -> bool { self.enabled == other.enabled && self.link_name == other.link_name && self.link_description == other.link_description @@ -323,7 +388,7 @@ pub struct SwitchInterfaceConfigCreate { } /// Indicates the kind for a switch interface. -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] #[serde(tag = "type", rename_all = "snake_case")] pub enum SwitchInterfaceKind { /// Primary interfaces are associated with physical links. There is exactly @@ -340,9 +405,62 @@ pub enum SwitchInterfaceKind { Loopback, } +/// Describes the kind of a switch interface. +// This type is the same as `SwitchInterfaceKind` except that the `Vlan` variant +// doesn't contain any details about the VLAN ID. This type is removed in a +// future API revision. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum SwitchInterfaceKindNoVlanDetails { + /// Primary interfaces are associated with physical links. There is exactly + /// one primary interface per physical link. + Primary, + + /// VLAN interfaces allow physical interfaces to be multiplexed onto + /// multiple logical links, each distinguished by a 12-bit 802.1Q Ethernet + /// tag. + Vlan, + + /// Loopback interfaces are anchors for IP addresses that are not specific + /// to any particular port. + Loopback, +} + +/// A switch port interface configuration for a port settings object. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct SwitchInterfaceConfig { + /// The port settings object this switch interface configuration belongs to. + pub port_settings_id: Uuid, + + /// A unique identifier for this switch interface. + pub id: Uuid, + + /// The name of this switch interface. + pub interface_name: Name, + + /// Whether or not IPv6 is enabled on this interface. + pub v6_enabled: bool, + + /// The switch interface kind. + pub kind: SwitchInterfaceKindNoVlanDetails, +} + +/// A switch port VLAN interface configuration for a port settings object. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct SwitchVlanInterfaceConfig { + /// The switch interface configuration this VLAN interface configuration + /// belongs to. + pub interface_config_id: Uuid, + + /// 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. + pub vlan_id: u16, +} + /// Configuration data associated with a switch VLAN interface. The VID /// indicates a VLAN identifier. Must be between 1 and 4096. -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] pub struct SwitchVlanInterface { /// The virtual network id (VID) that distinguishes this interface and is /// used for producing and consuming 802.1Q Ethernet tags. This field has a @@ -837,19 +955,19 @@ pub struct SwitchPortSettings { pub identity: IdentityMetadata, /// Switch port settings included from other switch port settings groups. - pub groups: Vec, + pub groups: Vec, /// Layer 1 physical port settings. - pub port: external::SwitchPortConfig, + pub port: SwitchPortConfig, /// Layer 2 link settings. - pub links: Vec, + pub links: Vec, /// Layer 3 interface settings. - pub interfaces: Vec, + pub interfaces: Vec, /// Vlan interface settings. - pub vlan_interfaces: Vec, + pub vlan_interfaces: Vec, /// IP route settings. pub routes: Vec, @@ -860,3 +978,70 @@ pub struct SwitchPortSettings { /// Layer 3 IP address settings. pub addresses: Vec, } + +/// The speed of a link. +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum LinkSpeed { + /// Zero gigabits per second. + Speed0G, + /// 1 gigabit per second. + Speed1G, + /// 10 gigabits per second. + Speed10G, + /// 25 gigabits per second. + Speed25G, + /// 40 gigabits per second. + Speed40G, + /// 50 gigabits per second. + Speed50G, + /// 100 gigabits per second. + Speed100G, + /// 200 gigabits per second. + Speed200G, + /// 400 gigabits per second. + Speed400G, +} + +/// The forward error correction mode of a link. +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum LinkFec { + /// Firecode forward error correction. + Firecode, + /// No forward error correction. + None, + /// Reed-Solomon forward error correction. + Rs, +} + +/// A link configuration for a port settings object. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct SwitchPortLinkConfig { + /// The port settings this link configuration belongs to. + pub port_settings_id: Uuid, + + /// The name of this link. + pub link_name: Name, + + /// The maximum transmission unit for this link. + pub mtu: u16, + + /// The requested forward-error correction method. If this is not + /// specified, the standard FEC for the underlying media will be applied + /// if it can be determined. + pub fec: Option, + + /// The configured speed of the link. + pub speed: LinkSpeed, + + /// Whether or not the link has autonegotiation enabled. + pub autoneg: bool, + + /// The link-layer discovery protocol service configuration for this + /// link. + pub lldp_link_config: Option, + + /// The tx_eq configuration for this link. + pub tx_eq_config: Option, +} diff --git a/nexus/types/versions/src/latest.rs b/nexus/types/versions/src/latest.rs index b38148305cd..951e80ea6c4 100644 --- a/nexus/types/versions/src/latest.rs +++ b/nexus/types/versions/src/latest.rs @@ -259,6 +259,9 @@ pub mod networking { pub use crate::v2025_11_20_00::networking::BgpRouteSelector; pub use crate::v2025_11_20_00::networking::BgpStatusSelector; pub use crate::v2025_11_20_00::networking::LinkConfigCreate; + pub use crate::v2025_11_20_00::networking::LinkFec; + pub use crate::v2025_11_20_00::networking::LinkSpeed; + pub use crate::v2025_11_20_00::networking::LldpLinkConfig; pub use crate::v2025_11_20_00::networking::LldpLinkConfigCreate; pub use crate::v2025_11_20_00::networking::Route; pub use crate::v2025_11_20_00::networking::RouteConfig; @@ -266,13 +269,19 @@ pub mod networking { pub use crate::v2025_11_20_00::networking::SwitchInterfaceConfigCreate; pub use crate::v2025_11_20_00::networking::SwitchInterfaceKind; pub use crate::v2025_11_20_00::networking::SwitchPortApplySettings; + pub use crate::v2025_11_20_00::networking::SwitchPortConfig; pub use crate::v2025_11_20_00::networking::SwitchPortConfigCreate; pub use crate::v2025_11_20_00::networking::SwitchPortGeometry; + pub use crate::v2025_11_20_00::networking::SwitchPortLinkConfig; pub use crate::v2025_11_20_00::networking::SwitchPortPageSelector; pub use crate::v2025_11_20_00::networking::SwitchPortPathSelector; + pub use crate::v2025_11_20_00::networking::SwitchPortSettingsGroup; + pub use crate::v2025_11_20_00::networking::SwitchPortSettingsGroups; + pub use crate::v2025_11_20_00::networking::SwitchPortSettingsIdentity; pub use crate::v2025_11_20_00::networking::SwitchPortSettingsInfoSelector; pub use crate::v2025_11_20_00::networking::SwitchPortSettingsSelector; pub use crate::v2025_11_20_00::networking::SwitchVlanInterface; + pub use crate::v2025_11_20_00::networking::SwitchVlanInterfaceConfig; pub use crate::v2025_11_20_00::networking::SwtichPortSettingsGroupCreate; pub use crate::v2025_12_12_00::networking::BgpPeerState; @@ -295,9 +304,11 @@ pub mod networking { pub use crate::v2026_04_16_00::networking::BgpPeer; pub use crate::v2026_04_16_00::networking::BgpPeerConfig; pub use crate::v2026_04_16_00::networking::BgpPeerConversionError; - pub use crate::v2026_04_16_00::networking::SwitchPortSettings; pub use crate::v2026_04_16_00::networking::SwitchPortSettingsCreate; pub use crate::v2026_04_16_00::networking::router_peer_type_try_from_old_representation; + + pub use crate::v2026_04_29_01::networking::SwitchInterfaceConfig; + pub use crate::v2026_04_29_01::networking::SwitchPortSettings; } pub mod oxql { diff --git a/nexus/types/versions/src/lib.rs b/nexus/types/versions/src/lib.rs index c16bcd11709..cb0e764efff 100644 --- a/nexus/types/versions/src/lib.rs +++ b/nexus/types/versions/src/lib.rs @@ -79,3 +79,5 @@ pub mod v2026_03_23_00; pub mod v2026_03_25_00; #[path = "stronger_bgp_unnumbered_types/mod.rs"] pub mod v2026_04_16_00; +#[path = "remove_duplicated_networking_types/mod.rs"] +pub mod v2026_04_29_01; diff --git a/nexus/types/versions/src/remove_duplicated_networking_types/mod.rs b/nexus/types/versions/src/remove_duplicated_networking_types/mod.rs new file mode 100644 index 00000000000..a314aa6362f --- /dev/null +++ b/nexus/types/versions/src/remove_duplicated_networking_types/mod.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Version `REMOVE_DUPLICATED_NETWORKING_TYPES` of the Nexus external API. + +pub mod networking; diff --git a/nexus/types/versions/src/remove_duplicated_networking_types/networking.rs b/nexus/types/versions/src/remove_duplicated_networking_types/networking.rs new file mode 100644 index 00000000000..b13ffdaf952 --- /dev/null +++ b/nexus/types/versions/src/remove_duplicated_networking_types/networking.rs @@ -0,0 +1,132 @@ +// 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/. + +//! Networking types for the `REMOVE_DUPLICATED_NETWORKING_TYPES` version. +//! +//! Changes in this version: +//! +//! * [`SwitchInterfaceConfig::kind`] is now [`SwitchInterfaceKind`] instead of +//! a different type with the same name but less information. +//! * New [`SwitchPortSettings`] version: +//! * Pick up the new [`SwitchInterfaceConfig`] +//! * Remove the `vlan_interfaces` field; the information it contained is +//! now embedded within [`SwitchPortSettings::interfaces`] (for any VLAN +//! interfaces). + +use crate::v2025_11_20_00::networking::SwitchInterfaceKind; +use crate::v2025_11_20_00::networking::SwitchInterfaceKindNoVlanDetails; +use crate::v2025_11_20_00::networking::SwitchPortConfig; +use crate::v2025_11_20_00::networking::SwitchPortLinkConfig; +use crate::v2025_11_20_00::networking::SwitchPortSettingsGroups; +use crate::v2025_11_20_00::networking::SwitchVlanInterface; +use crate::v2025_11_20_00::networking::SwitchVlanInterfaceConfig; +use crate::v2026_04_16_00::networking::BgpPeer; +use omicron_common::api::external; +use omicron_common::api::external::IdentityMetadata; +use omicron_common::api::external::Name; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use uuid::Uuid; + +/// A switch port interface configuration for a port settings object. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct SwitchInterfaceConfig { + /// The port settings object this switch interface configuration belongs to. + pub port_settings_id: Uuid, + + /// A unique identifier for this switch interface. + pub id: Uuid, + + /// The name of this switch interface. + pub interface_name: Name, + + /// Whether or not IPv6 is enabled on this interface. + pub v6_enabled: bool, + + /// The switch interface kind. + pub kind: SwitchInterfaceKind, +} + +/// 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. +// TODO: several fields below embed `external::*` types directly from +// `omicron-common`, which means their serialized shape is not truly frozen. +// Once `omicron-common-versions` exists, replace these with version-local +// copies of the types to ensure the initial version's wire format is +// immutable. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct SwitchPortSettings { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// Switch port settings included from other switch port settings groups. + pub groups: Vec, + + /// Layer 1 physical port settings. + pub port: SwitchPortConfig, + + /// Layer 2 link settings. + pub links: Vec, + + /// Layer 3 interface settings. + pub interfaces: Vec, + + /// IP route settings. + pub routes: Vec, + + /// BGP peer settings. + pub bgp_peers: Vec, + + /// Layer 3 IP address settings. + pub addresses: Vec, +} + +impl From + for crate::v2026_04_16_00::networking::SwitchPortSettings +{ + fn from(value: SwitchPortSettings) -> Self { + let mut interfaces = Vec::with_capacity(value.interfaces.len()); + let mut vlan_interfaces = Vec::new(); + for iface in value.interfaces { + let kind = match iface.kind { + SwitchInterfaceKind::Primary => { + SwitchInterfaceKindNoVlanDetails::Primary + } + SwitchInterfaceKind::Loopback => { + SwitchInterfaceKindNoVlanDetails::Loopback + } + SwitchInterfaceKind::Vlan(SwitchVlanInterface { vid }) => { + vlan_interfaces.push(SwitchVlanInterfaceConfig { + interface_config_id: iface.id, + vlan_id: vid, + }); + SwitchInterfaceKindNoVlanDetails::Vlan + } + }; + interfaces.push( + crate::v2025_11_20_00::networking::SwitchInterfaceConfig { + port_settings_id: iface.port_settings_id, + id: iface.id, + interface_name: iface.interface_name, + v6_enabled: iface.v6_enabled, + kind, + }, + ); + } + + Self { + identity: value.identity, + groups: value.groups, + port: value.port, + links: value.links, + interfaces, + vlan_interfaces, + routes: value.routes, + bgp_peers: value.bgp_peers, + addresses: value.addresses, + } + } +} diff --git a/nexus/types/versions/src/stronger_bgp_unnumbered_types/networking.rs b/nexus/types/versions/src/stronger_bgp_unnumbered_types/networking.rs index 587eebc77e6..2c4f5ddcbd9 100644 --- a/nexus/types/versions/src/stronger_bgp_unnumbered_types/networking.rs +++ b/nexus/types/versions/src/stronger_bgp_unnumbered_types/networking.rs @@ -19,8 +19,9 @@ //! * [`SwitchPortSettingsCreate`] use crate::v2025_11_20_00::networking::{ - AddressConfig, LinkConfigCreate, RouteConfig, SwitchInterfaceConfigCreate, - SwitchPortConfigCreate, + AddressConfig, LinkConfigCreate, RouteConfig, SwitchInterfaceConfig, + SwitchInterfaceConfigCreate, SwitchPortConfig, SwitchPortConfigCreate, + SwitchPortLinkConfig, SwitchPortSettingsGroups, SwitchVlanInterfaceConfig, }; use omicron_common::api::external; use omicron_common::api::external::IdentityMetadata; @@ -331,19 +332,19 @@ pub struct SwitchPortSettings { pub identity: IdentityMetadata, /// Switch port settings included from other switch port settings groups. - pub groups: Vec, + pub groups: Vec, /// Layer 1 physical port settings. - pub port: external::SwitchPortConfig, + pub port: SwitchPortConfig, /// Layer 2 link settings. - pub links: Vec, + pub links: Vec, /// Layer 3 interface settings. - pub interfaces: Vec, + pub interfaces: Vec, /// Vlan interface settings. - pub vlan_interfaces: Vec, + pub vlan_interfaces: Vec, /// IP route settings. pub routes: Vec, diff --git a/openapi/nexus/nexus-2026042900.0.0-45479d.json.gitstub b/openapi/nexus/nexus-2026042900.0.0-45479d.json.gitstub new file mode 100644 index 00000000000..b4d7707a22f --- /dev/null +++ b/openapi/nexus/nexus-2026042900.0.0-45479d.json.gitstub @@ -0,0 +1 @@ +af27b4d9e33e3381d0d9470e6bcc5ad364995ca2:openapi/nexus/nexus-2026042900.0.0-45479d.json diff --git a/openapi/nexus/nexus-2026042900.0.0-45479d.json b/openapi/nexus/nexus-2026042901.0.0-de40a9.json similarity index 99% rename from openapi/nexus/nexus-2026042900.0.0-45479d.json rename to openapi/nexus/nexus-2026042901.0.0-de40a9.json index 7924cdad8d3..deb73975291 100644 --- a/openapi/nexus/nexus-2026042900.0.0-45479d.json +++ b/openapi/nexus/nexus-2026042901.0.0-de40a9.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "2026042900.0.0" + "version": "2026042901.0.0" }, "paths": { "/device/auth": { @@ -28595,7 +28595,7 @@ "description": "The switch interface kind.", "allOf": [ { - "$ref": "#/components/schemas/SwitchInterfaceKind2" + "$ref": "#/components/schemas/SwitchInterfaceKind" } ] }, @@ -28705,32 +28705,6 @@ } ] }, - "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": {}, "SwitchPort": { "description": "A switch port represents a physical external port on a rack switch.", @@ -28861,7 +28835,7 @@ "description": "The physical link geometry of the port.", "allOf": [ { - "$ref": "#/components/schemas/SwitchPortGeometry2" + "$ref": "#/components/schemas/SwitchPortGeometry" } ] }, @@ -28919,32 +28893,6 @@ } ] }, - "SwitchPortGeometry2": { - "description": "The link geometry associated with a switch port.", - "oneOf": [ - { - "description": "The port contains a single QSFP28 link with four lanes.", - "type": "string", - "enum": [ - "qsfp28x1" - ] - }, - { - "description": "The port contains two QSFP28 links each with two lanes.", - "type": "string", - "enum": [ - "qsfp28x2" - ] - }, - { - "description": "The port contains four SFP28 links each with one lane.", - "type": "string", - "enum": [ - "sfp28x4" - ] - } - ] - }, "SwitchPortLinkConfig": { "description": "A link configuration for a port settings object.", "type": "object", @@ -29003,7 +28951,7 @@ "description": "The tx_eq configuration for this link.", "allOf": [ { - "$ref": "#/components/schemas/TxEqConfig2" + "$ref": "#/components/schemas/TxEqConfig" } ] } @@ -29169,13 +29117,6 @@ "description": "Timestamp when this resource was last modified", "type": "string", "format": "date-time" - }, - "vlan_interfaces": { - "description": "Vlan interface settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchVlanInterfaceConfig" - } } }, "required": [ @@ -29190,8 +29131,7 @@ "port", "routes", "time_created", - "time_modified", - "vlan_interfaces" + "time_modified" ] }, "SwitchPortSettingsCreate": { @@ -29382,27 +29322,6 @@ } ] }, - "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" - ] - }, "TargetRelease": { "description": "View of a system software target release", "type": "object", @@ -29667,42 +29586,6 @@ } } }, - "TxEqConfig2": { - "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", - "type": "object", - "properties": { - "main": { - "nullable": true, - "description": "Main tap", - "type": "integer", - "format": "int32" - }, - "post1": { - "nullable": true, - "description": "Post-cursor tap1", - "type": "integer", - "format": "int32" - }, - "post2": { - "nullable": true, - "description": "Post-cursor tap2", - "type": "integer", - "format": "int32" - }, - "pre1": { - "nullable": true, - "description": "Pre-cursor tap1", - "type": "integer", - "format": "int32" - }, - "pre2": { - "nullable": true, - "description": "Pre-cursor tap2", - "type": "integer", - "format": "int32" - } - } - }, "UninitializedSled": { "description": "A sled that has not been added to an initialized rack yet", "type": "object", diff --git a/openapi/nexus/nexus-latest.json b/openapi/nexus/nexus-latest.json index b80184b7c35..91e9ce1548b 120000 --- a/openapi/nexus/nexus-latest.json +++ b/openapi/nexus/nexus-latest.json @@ -1 +1 @@ -nexus-2026042900.0.0-45479d.json \ No newline at end of file +nexus-2026042901.0.0-de40a9.json \ No newline at end of file diff --git a/sled-agent/types/versions/src/impls/early_networking.rs b/sled-agent/types/versions/src/impls/early_networking.rs index 53b95bd0c89..2388535df86 100644 --- a/sled-agent/types/versions/src/impls/early_networking.rs +++ b/sled-agent/types/versions/src/impls/early_networking.rs @@ -21,7 +21,6 @@ use crate::latest::early_networking::UplinkAddress; use crate::latest::early_networking::UplinkAddressConfig; use crate::latest::early_networking::UplinkIpNet; use crate::latest::early_networking::UplinkIpNetError; -use omicron_common::api::external; use oxnet::IpNet; use oxnet::IpNetParseError; use oxnet::Ipv6Net; @@ -69,32 +68,6 @@ impl BgpPeerConfig { } } -impl From for external::LinkFec { - fn from(x: PortFec) -> Self { - match x { - PortFec::Firecode => Self::Firecode, - PortFec::None => Self::None, - PortFec::Rs => Self::Rs, - } - } -} - -impl From for external::LinkSpeed { - fn from(x: PortSpeed) -> Self { - match x { - PortSpeed::Speed0G => Self::Speed0G, - PortSpeed::Speed1G => Self::Speed1G, - PortSpeed::Speed10G => Self::Speed10G, - PortSpeed::Speed25G => Self::Speed25G, - PortSpeed::Speed40G => Self::Speed40G, - PortSpeed::Speed50G => Self::Speed50G, - PortSpeed::Speed100G => Self::Speed100G, - PortSpeed::Speed200G => Self::Speed200G, - PortSpeed::Speed400G => Self::Speed400G, - } - } -} - impl FromStr for MaxPathConfig { type Err = MaxPathConfigError;