diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 16803546f79..387e2acdb4c 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2466,7 +2466,7 @@ pub struct SwitchPort { #[derive( ObjectIdentity, Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq, )] -pub struct SwitchPortSettings { +pub struct SwitchPortSettingsIdentity { #[serde(flatten)] pub identity: IdentityMetadata, } @@ -2475,9 +2475,9 @@ pub struct SwitchPortSettings { /// convenience data structure for getting a complete view of a particular /// port's settings. #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct SwitchPortSettingsView { - /// The primary switch port settings handle. - pub settings: SwitchPortSettings, +pub struct SwitchPortSettings { + #[serde(flatten)] + pub identity: IdentityMetadata, /// Switch port settings included from other switch port settings groups. pub groups: Vec, @@ -2488,13 +2488,6 @@ pub struct SwitchPortSettingsView { /// Layer 2 link settings. pub links: Vec, - /// Link-layer discovery protocol (LLDP) settings. - pub link_lldp: Vec, - - /// TX equalization settings. These are optional, and most links will not - /// need them. - pub tx_eq: Vec>, - /// Layer 3 interface settings. pub interfaces: Vec, @@ -2508,7 +2501,7 @@ pub struct SwitchPortSettingsView { pub bgp_peers: Vec, /// Layer 3 IP address settings. - pub addresses: Vec, + pub addresses: Vec, } /// This structure maps a port settings object to a port settings groups. Port @@ -2633,13 +2626,6 @@ pub struct SwitchPortLinkConfig { /// The port settings this link configuration belongs to. pub port_settings_id: Uuid, - /// The link-layer discovery protocol service configuration id for this - /// link. - pub lldp_link_config_id: Option, - - /// The tx_eq configuration id for this link. - pub tx_eq_config_id: Option, - /// The name of this link. pub link_name: String, @@ -2656,6 +2642,13 @@ pub struct SwitchPortLinkConfig { /// 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. @@ -2683,7 +2676,7 @@ pub struct LldpLinkConfig { pub system_description: Option, /// The LLDP management IP TLV. - pub management_ip: Option, + pub management_ip: Option, } /// Information about LLDP advertisements from other network entities directly @@ -2746,18 +2739,6 @@ pub struct TxEqConfig { pub post1: Option, } -impl From for TxEqConfig { - fn from(x: crate::api::internal::shared::TxEqConfig) -> TxEqConfig { - TxEqConfig { - pre1: x.pre1, - pre2: x.pre2, - main: x.main, - post2: x.post2, - post1: x.post1, - } - } -} - /// Describes the kind of an switch interface. #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] #[serde(rename_all = "snake_case")] @@ -2825,7 +2806,7 @@ pub struct SwitchPortRouteConfig { pub dst: oxnet::IpNet, /// The route's gateway address. - pub gw: oxnet::IpNet, + pub gw: IpAddr, /// The VLAN identifier for the route. Use this if the gateway is reachable /// over an 802.1Q tagged L2 segment. @@ -2981,6 +2962,33 @@ pub struct SwitchPortAddressConfig { pub interface_name: String, } +/// An IP address configuration for a port settings object. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct SwitchPortAddressView { + /// The port settings object this address configuration belongs to. + pub port_settings_id: Uuid, + + /// The id of the address lot this address is drawn from. + pub address_lot_id: Uuid, + + /// The name of the address lot this address is drawn from. + pub address_lot_name: Name, + + /// The id of the address lot block this address is drawn from. + pub address_lot_block_id: Uuid, + + /// The IP address and prefix. + pub address: oxnet::IpNet, + + /// An optional VLAN ID + pub vlan_id: Option, + + /// The interface name this address belongs to. + // TODO: https://github.com/oxidecomputer/omicron/issues/3050 + // Use `Name` instead of `String` for `interface_name` type + pub interface_name: String, +} + /// The current state of a BGP peer. #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] #[serde(rename_all = "snake_case")] diff --git a/nexus/db-model/src/switch_port.rs b/nexus/db-model/src/switch_port.rs index 0bca70dddfa..6a505012d4d 100644 --- a/nexus/db-model/src/switch_port.rs +++ b/nexus/db-model/src/switch_port.rs @@ -47,6 +47,28 @@ impl_enum_type!( Sfp28x4 => b"Sfp28x4" ); +impl PartialEq for SwitchPortGeometry { + fn eq(&self, other: ¶ms::SwitchPortGeometry) -> bool { + match self { + Self::Qsfp28x1 => { + return matches!(other, params::SwitchPortGeometry::Qsfp28x1); + } + Self::Qsfp28x2 => { + return matches!(other, params::SwitchPortGeometry::Qsfp28x2); + } + Self::Sfp28x4 => { + return matches!(other, params::SwitchPortGeometry::Sfp28x4); + } + } + } +} + +impl PartialEq for params::SwitchPortGeometry { + fn eq(&self, other: &SwitchPortGeometry) -> bool { + other.eq(self) + } +} + impl_enum_type!( SwitchLinkFecEnum: @@ -286,9 +308,9 @@ impl SwitchPortSettings { } } -impl Into for SwitchPortSettings { - fn into(self) -> external::SwitchPortSettings { - external::SwitchPortSettings { identity: self.identity() } +impl Into for SwitchPortSettings { + fn into(self) -> external::SwitchPortSettingsIdentity { + external::SwitchPortSettingsIdentity { identity: self.identity() } } } @@ -407,21 +429,6 @@ impl SwitchPortLinkConfig { } } -impl Into for SwitchPortLinkConfig { - fn into(self) -> external::SwitchPortLinkConfig { - external::SwitchPortLinkConfig { - port_settings_id: self.port_settings_id, - lldp_link_config_id: self.lldp_link_config_id, - tx_eq_config_id: self.tx_eq_config_id, - link_name: self.link_name.clone(), - mtu: self.mtu.into(), - fec: self.fec.map(|fec| fec.into()), - speed: self.speed.into(), - autoneg: self.autoneg, - } - } -} - #[derive( Queryable, Insertable, @@ -486,7 +493,7 @@ impl Into for LldpLinkConfig { chassis_id: self.chassis_id.clone(), system_name: self.system_name.clone(), system_description: self.system_description.clone(), - management_ip: self.management_ip.map(|a| a.into()), + management_ip: self.management_ip.map(|a| a.ip()), } } } @@ -625,7 +632,7 @@ impl Into for SwitchPortRouteConfig { port_settings_id: self.port_settings_id, interface_name: self.interface_name.clone(), dst: self.dst.into(), - gw: self.gw.into(), + gw: self.gw.ip(), vlan_id: self.vid.map(Into::into), rib_priority: self.rib_priority.map(Into::into), } diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index b892a03f7b8..14501701cd9 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -12,11 +12,11 @@ use crate::db::datastore::address_lot::{ ReserveBlockError, ReserveBlockTxnError, }; use crate::db::model::{ - LldpLinkConfig, Name, SwitchInterfaceConfig, SwitchPort, - SwitchPortAddressConfig, SwitchPortBgpPeerConfig, SwitchPortConfig, - SwitchPortLinkConfig, SwitchPortRouteConfig, SwitchPortSettings, - SwitchPortSettingsGroup, SwitchPortSettingsGroups, - SwitchVlanInterfaceConfig, TxEqConfig, + LldpLinkConfig, Name, SwitchInterfaceConfig, SwitchLinkFec, + SwitchLinkSpeed, SwitchPort, SwitchPortAddressConfig, + SwitchPortBgpPeerConfig, SwitchPortConfig, SwitchPortLinkConfig, + SwitchPortRouteConfig, SwitchPortSettings, SwitchPortSettingsGroup, + SwitchPortSettingsGroups, SwitchVlanInterfaceConfig, TxEqConfig, }; use crate::db::pagination::paginated; use async_bb8_diesel::{AsyncRunQueryDsl, Connection}; @@ -30,15 +30,17 @@ use nexus_db_errors::ErrorHandler; use nexus_db_errors::OptionalError; use nexus_db_errors::public_error_from_diesel; use nexus_db_model::{ - BgpConfig, SqlU8, SqlU16, SqlU32, SwitchPortBgpPeerConfigAllowExport, - SwitchPortBgpPeerConfigAllowImport, SwitchPortBgpPeerConfigCommunity, + AddressLot, BgpConfig, SqlU8, SqlU16, SqlU32, + SwitchPortBgpPeerConfigAllowExport, SwitchPortBgpPeerConfigAllowImport, + SwitchPortBgpPeerConfigCommunity, }; use nexus_types::external_api::params; +use nexus_types::identity::Resource; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ self, CreateResult, DataPageParams, DeleteResult, Error, ImportExportPolicy, ListResultVec, LookupResult, NameOrId, ResourceType, - UpdateResult, + SwitchPortAddressView, UpdateResult, }; use ref_cast::RefCast; use serde::{Deserialize, Serialize}; @@ -99,14 +101,14 @@ pub struct SwitchPortSettingsCombinedResult { pub settings: SwitchPortSettings, pub groups: Vec, pub port: SwitchPortConfig, - pub links: Vec, + pub links: Vec, pub link_lldp: Vec, - pub tx_eq: Vec>, + pub tx_eq: Vec, pub interfaces: Vec, pub vlan_interfaces: Vec, pub routes: Vec, pub bgp_peers: Vec, - pub addresses: Vec, + pub addresses: Vec, } impl SwitchPortSettingsCombinedResult { @@ -127,21 +129,13 @@ impl SwitchPortSettingsCombinedResult { } } -impl Into - for SwitchPortSettingsCombinedResult -{ - fn into(self) -> external::SwitchPortSettingsView { - external::SwitchPortSettingsView { - settings: self.settings.into(), +impl Into for SwitchPortSettingsCombinedResult { + fn into(self) -> external::SwitchPortSettings { + external::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(), - link_lldp: self.link_lldp.into_iter().map(Into::into).collect(), - tx_eq: self - .tx_eq - .into_iter() - .map(|t| if let Some(t) = t { Some(t.into()) } else { None }) - .collect(), interfaces: self.interfaces.into_iter().map(Into::into).collect(), vlan_interfaces: self .vlan_interfaces @@ -150,7 +144,7 @@ impl 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.into_iter().map(Into::into).collect(), + addresses: self.addresses, } } } @@ -161,6 +155,33 @@ pub struct SwitchPortSettingsGroupCreateResult { pub settings: SwitchPortSettingsCombinedResult, } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct LinkConfigCombinedResult { + pub port_settings_id: Uuid, + pub link_name: String, + pub mtu: SqlU16, + pub fec: Option, + pub speed: SwitchLinkSpeed, + pub autoneg: bool, + pub lldp_link_config: Option, + pub tx_eq_config: Option, +} + +impl From for external::SwitchPortLinkConfig { + fn from(value: LinkConfigCombinedResult) -> Self { + Self { + port_settings_id: value.port_settings_id, + link_name: value.link_name, + mtu: *value.mtu, + fec: value.fec.map(Into::into), + speed: value.speed.into(), + autoneg: value.autoneg, + lldp_link_config: value.lldp_link_config.map(Into::into), + tx_eq_config: value.tx_eq_config.map(Into::into), + } + } +} + impl DataStore { pub async fn switch_port_settings_exist( &self, @@ -468,14 +489,13 @@ impl DataStore { self as link_config, dsl as link_config_dsl, }; - result.links = link_config_dsl::switch_port_settings_link_config + let link_configs = link_config_dsl::switch_port_settings_link_config .filter(link_config::port_settings_id.eq(id)) .select(SwitchPortLinkConfig::as_select()) .load_async::(&conn) .await?; - let lldp_link_ids: Vec = result - .links + let lldp_link_ids: Vec = link_configs .iter() .filter_map(|link| link.lldp_link_config_id) .collect(); @@ -488,14 +508,14 @@ impl DataStore { .load_async::(&conn) .await?; - let tx_eq_ids_and_nulls :Vec>= result - .links + let tx_eq_ids_and_nulls :Vec>= link_configs .iter() - .map(|link| link.tx_eq_config_id) + .map(|link| link.tx_eq_config_id ) .collect(); + let tx_eq_ids: Vec = tx_eq_ids_and_nulls .iter() - .cloned() + .cloned() .flatten() .collect(); @@ -506,12 +526,43 @@ impl DataStore { .limit(1) .load_async::(&conn) .await?; - result.tx_eq = tx_eq_ids_and_nulls.iter().map(|x| - if let Some(id) = x { - configs.iter().find(|c| c.id == *id).cloned() - } else { - None - }).collect(); + result.tx_eq = tx_eq_ids_and_nulls + .iter() + .filter_map(|x| + if let Some(id) = x { + configs.iter().find(|c| c.id == *id).cloned()} + else { + None + }) + .collect(); + + result.links = link_configs + .into_iter() + .map(|config| { + let lldp_link_config = match config.lldp_link_config_id { + Some(id) => result.link_lldp.iter().find(|c| c.id == id), + None => None, + } + .cloned(); + + let tx_eq_config = match config.tx_eq_config_id { + Some(id) => configs.iter().find(|c| c.id == id), + None => None, + } + .cloned(); + + LinkConfigCombinedResult { + port_settings_id: config.port_settings_id, + link_name: config.link_name, + mtu: config.mtu, + fec: config.fec, + speed: config.speed, + autoneg: config.autoneg, + lldp_link_config, + tx_eq_config, + } + }) + .collect(); // get the interface configs use nexus_db_schema::schema::switch_port_settings_interface_config::{ @@ -640,13 +691,14 @@ impl DataStore { self as address_config, dsl as address_config_dsl, }; - result.addresses = - address_config_dsl::switch_port_settings_address_config + let addresses = address_config_dsl::switch_port_settings_address_config .filter(address_config::port_settings_id.eq(id)) .select(SwitchPortAddressConfig::as_select()) .load_async::(&conn) .await?; + result.addresses = switch_port_address_view(&conn, addresses).await?; + Ok(result) } }) @@ -1211,7 +1263,7 @@ async fn do_switch_port_settings_create( let mut link_config = Vec::with_capacity(params.links.len()); let mut tx_eq_config = Vec::with_capacity(params.links.len()); - for (link_name, c) in ¶ms.links { + for c in ¶ms.links { let lldp_link_config = LldpLinkConfig::new( c.lldp.enabled, c.lldp.link_name.clone(), @@ -1229,19 +1281,16 @@ async fn do_switch_port_settings_create( let config = TxEqConfig::new(t.pre1, t.pre2, t.main, t.post2, t.post1); let tx_eq_config_id = config.id; - tx_eq_config.push(Some(config)); + tx_eq_config.push(config); Some(tx_eq_config_id) } - _ => { - tx_eq_config.push(None); - None - } + _ => None, }; link_config.push(SwitchPortLinkConfig::new( psid, lldp_config_id, - link_name.clone(), + c.link_name.to_string(), c.mtu, c.fec.map(|fec| fec.into()), c.speed.into(), @@ -1256,29 +1305,54 @@ async fn do_switch_port_settings_create( .get_results_async(conn) .await?; - // We want to insert the Some(config) values into the table, but preserve the - // full vector of None/Some values. - let v: Vec = tx_eq_config.iter().flatten().cloned().collect(); let _ = diesel::insert_into(tx_eq_config_dsl::tx_eq_config) - .values(v) + .values(tx_eq_config.clone()) .returning(TxEqConfig::as_returning()) .get_results_async(conn) .await?; result.tx_eq = tx_eq_config; - result.links = + let link_configs = diesel::insert_into(link_config_dsl::switch_port_settings_link_config) .values(link_config) .returning(SwitchPortLinkConfig::as_returning()) .get_results_async(conn) .await?; + result.links = link_configs + .into_iter() + .map(|config| { + let lldp_link_config = match config.lldp_link_config_id { + Some(id) => result.link_lldp.iter().find(|c| c.id == id), + None => None, + } + .cloned(); + + let tx_eq_config = match config.tx_eq_config_id { + Some(id) => result.tx_eq.iter().find(|c| c.id == id), + None => None, + } + .cloned(); + + LinkConfigCombinedResult { + port_settings_id: config.port_settings_id, + link_name: config.link_name, + mtu: config.mtu, + fec: config.fec, + speed: config.speed, + autoneg: config.autoneg, + lldp_link_config, + tx_eq_config, + } + }) + .collect(); + let mut interface_config = Vec::with_capacity(params.interfaces.len()); let mut vlan_interface_config = Vec::new(); - for (interface_name, i) in ¶ms.interfaces { + for i in ¶ms.interfaces { let ifx_config = SwitchInterfaceConfig::new( psid, - interface_name.clone(), + i.link_name.to_string(), i.v6_enabled, i.kind.into(), ); @@ -1306,11 +1380,11 @@ async fn do_switch_port_settings_create( let mut route_config = Vec::with_capacity(params.routes.len()); - for (interface_name, r) in ¶ms.routes { + for r in ¶ms.routes { for route in &r.routes { route_config.push(SwitchPortRouteConfig::new( psid, - interface_name.clone(), + r.link_name.to_string(), route.dst.into(), route.gw.into(), route.vid.map(Into::into), @@ -1330,7 +1404,7 @@ async fn do_switch_port_settings_create( BTreeMap::new(); let mut bgp_peer_config = Vec::new(); - for (interface_name, peer_config) in ¶ms.bgp_peers { + for peer_config in ¶ms.bgp_peers { for p in &peer_config.peers { peer_by_addr.insert(p.addr, &p); use nexus_db_schema::schema::bgp_config; @@ -1373,7 +1447,7 @@ async fn do_switch_port_settings_create( .into_iter() .map(|x| SwitchPortBgpPeerConfigAllowImport { port_settings_id: id, - interface_name: interface_name.clone(), + interface_name: peer_config.link_name.to_string(), addr: p.addr.into(), prefix: x.into(), }) @@ -1392,7 +1466,7 @@ async fn do_switch_port_settings_create( .into_iter() .map(|x| SwitchPortBgpPeerConfigAllowExport { port_settings_id: id, - interface_name: interface_name.clone(), + interface_name: peer_config.link_name.to_string(), addr: p.addr.into(), prefix: x.into(), }) @@ -1412,7 +1486,7 @@ async fn do_switch_port_settings_create( .into_iter() .map(|x| SwitchPortBgpPeerConfigCommunity { port_settings_id: id, - interface_name: interface_name.clone(), + interface_name: peer_config.link_name.to_string(), addr: p.addr.into(), community: x.into(), }) @@ -1427,7 +1501,7 @@ async fn do_switch_port_settings_create( bgp_peer_config.push(SwitchPortBgpPeerConfig::new( psid, bgp_config_id, - interface_name.clone(), + peer_config.link_name.to_string(), p, )); } @@ -1479,7 +1553,7 @@ async fn do_switch_port_settings_create( let mut address_config = Vec::new(); use nexus_db_schema::schema::address_lot; - for (interface_name, a) in ¶ms.addresses { + for a in ¶ms.addresses { for address in &a.addresses { let address_lot_id = match &address.address_lot { NameOrId::Id(id) => address_lot::table @@ -1536,12 +1610,12 @@ async fn do_switch_port_settings_create( block.id, rsvd_block.id, address.address.into(), - interface_name.clone(), + a.link_name.to_string(), address.vlan_id, )); } } - result.addresses = diesel::insert_into( + let addresses = diesel::insert_into( address_config_dsl::switch_port_settings_address_config, ) .values(address_config) @@ -1549,6 +1623,42 @@ async fn do_switch_port_settings_create( .get_results_async(conn) .await?; + result.addresses = switch_port_address_view(conn, addresses).await?; + + Ok(result) +} + +async fn switch_port_address_view( + conn: &Connection>, + addresses: Vec, +) -> Result, diesel::result::Error> { + use nexus_db_schema::schema::{address_lot, address_lot_block}; + + let mut result = vec![]; + + for address in addresses { + let lot = address_lot::table + .inner_join( + address_lot_block::table + .on(address_lot_block::address_lot_id.eq(address_lot::id)), + ) + .filter(address_lot_block::id.eq(address.address_lot_block_id)) + .select(AddressLot::as_select()) + .limit(1) + .first_async::(conn) + .await?; + + result.push(SwitchPortAddressView { + port_settings_id: address.port_settings_id, + address_lot_id: lot.id(), + address_lot_name: lot.name().clone(), + address_lot_block_id: address.address_lot_block_id, + address: address.address.into(), + vlan_id: address.vlan_id.map(Into::into), + interface_name: address.interface_name, + }) + } + Ok(result) } @@ -1752,7 +1862,7 @@ mod test { NameOrId, }; use omicron_test_utils::dev; - use std::collections::HashMap; + use std::{collections::HashMap, str::FromStr}; use uuid::Uuid; #[tokio::test] @@ -1806,37 +1916,36 @@ mod test { geometry: SwitchPortGeometry::Qsfp28x1, }, groups: Vec::new(), - links: HashMap::new(), - interfaces: HashMap::new(), - routes: HashMap::new(), - bgp_peers: HashMap::from([( - "phy0".into(), - BgpPeerConfig { - peers: vec![BgpPeer { - bgp_config: NameOrId::Name( - "test-bgp-config".parse().unwrap(), - ), - interface_name: "qsfp0".into(), - addr: "192.168.1.1".parse().unwrap(), - hold_time: 0, - idle_hold_time: 0, - delay_open: 0, - connect_retry: 0, - keepalive: 0, - remote_asn: None, - min_ttl: None, - md5_auth_key: None, - multi_exit_discriminator: None, - communities: Vec::new(), - local_pref: None, - enforce_first_as: false, - allowed_export: ImportExportPolicy::NoFiltering, - allowed_import: ImportExportPolicy::NoFiltering, - vlan_id: None, - }], - }, - )]), - addresses: HashMap::new(), + links: vec![], + interfaces: vec![], + routes: vec![], + bgp_peers: vec![BgpPeerConfig { + link_name: Name::from_str("phy0") + .expect("phy0 should be a valid link name"), + peers: vec![BgpPeer { + bgp_config: NameOrId::Name( + "test-bgp-config".parse().unwrap(), + ), + interface_name: "qsfp0".into(), + addr: "192.168.1.1".parse().unwrap(), + hold_time: 0, + idle_hold_time: 0, + delay_open: 0, + connect_retry: 0, + keepalive: 0, + remote_asn: None, + min_ttl: None, + md5_auth_key: None, + multi_exit_discriminator: None, + communities: Vec::new(), + local_pref: None, + enforce_first_as: false, + allowed_export: ImportExportPolicy::NoFiltering, + allowed_import: ImportExportPolicy::NoFiltering, + vlan_id: None, + }], + }], + addresses: vec![], }; let settings_result = datastore @@ -1859,6 +1968,221 @@ mod test { assert_eq!(uplink_ports.len(), 1); + let db_settings_id = uplink_ports[0] + .port_settings_id + .expect("should have a port settings id"); + + let db_settings = datastore + .switch_port_settings_get(&opctx, &NameOrId::Id(db_settings_id)) + .await + .unwrap(); + + assert_eq!(settings.identity.name, *db_settings.settings.identity.name); + + assert_eq!( + settings.identity.description, + db_settings.settings.identity.description + ); + + assert_eq!(settings.port_config.geometry, db_settings.port.geometry); + + assert_eq!( + db_settings.groups.len(), + 0, + "groups are not currently used and should be empty" + ); + + assert_eq!(settings.links.len(), db_settings.links.len()); + let mut db_links = HashMap::new(); + + for link in db_settings.links { + db_links.insert(link.link_name.clone(), link); + } + + for link in settings.links { + let db_link = db_links + .get(&link.link_name.to_string()) + .expect("requested link should be present"); + + assert_eq!(link.mtu, *db_link.mtu); + assert_eq!(link.fec, db_link.fec.map(Into::into)); + assert_eq!(link.speed, db_link.speed.into()); + assert_eq!(link.autoneg, db_link.autoneg); + + match &db_link.lldp_link_config { + Some(config) => { + assert_eq!( + config.link_description, + link.lldp.link_description + ); + + assert_eq!(config.chassis_id, link.lldp.chassis_id); + + assert_eq!(config.system_name, link.lldp.system_name); + + assert_eq!( + config.system_description, + link.lldp.system_description + ); + + assert_eq!( + config.management_ip.map(|n| n.ip()), + link.lldp.management_ip + ); + } + None => { + unreachable!( + "we currently always create a LLDP configuration for a link" + ); + } + } + + match &db_link.tx_eq_config { + Some(config) => { + let requested_config = link.tx_eq.expect("if tx_eq config is present in db it should be present in request"); + // stuff + // pub pre1: Option, + assert_eq!(config.pre1, requested_config.pre1); + + // pub pre2: Option, + assert_eq!(config.pre2, requested_config.pre2); + + // pub main: Option, + assert_eq!(config.main, requested_config.main); + + // pub post2: Option, + assert_eq!(config.post2, requested_config.post2); + + // pub post1: Option, + assert_eq!(config.post1, requested_config.post1); + } + None => { + assert!( + link.tx_eq.is_none(), + "if tx_eq config is absent in db it should be absent in request" + ) + } + } + } + + assert_eq!( + db_settings.interfaces.len(), + 0, + "interfaces are not currently used and should be empty" + ); + + assert_eq!(settings.routes.len(), db_settings.routes.len()); + let mut db_routes = HashMap::new(); + + for route in db_settings.routes { + db_routes.insert( + (route.interface_name.clone(), route.dst, route.gw.ip()), + route, + ); + } + + for config in settings.routes { + for route in config.routes { + let db_route = db_routes + .get(&( + config.link_name.to_string(), + route.dst.into(), + route.gw, + )) + .expect("requested route should be present"); + + assert_eq!( + db_route.rib_priority.map(|p| *p), + route.rib_priority + ); + + assert_eq!(db_route.vid.map(|v| *v), route.vid); + } + } + + assert_eq!(settings.addresses.len(), db_settings.addresses.len()); + let mut db_addresses = HashMap::new(); + + for address in db_settings.addresses { + db_addresses.insert(address.interface_name.clone(), address); + } + + for config in settings.addresses { + let db_address = db_addresses + .get(&config.link_name.to_string()) + .expect("requested address should be present"); + + for address in config.addresses { + assert_eq!(db_address.address, address.address); + + assert_eq!(db_address.vlan_id, address.vlan_id); + + match address.address_lot { + NameOrId::Id(id) => { + assert_eq!(db_address.address_lot_id, id); + } + NameOrId::Name(name) => { + assert_eq!(db_address.address_lot_name, name); + } + } + } + } + + assert_eq!(settings.bgp_peers.len(), db_settings.bgp_peers.len()); + let mut db_peers = HashMap::new(); + + for peer in db_settings.bgp_peers { + db_peers.insert(peer.interface_name.clone(), peer); + } + + for config in settings.bgp_peers { + let db_peer = db_peers + .get(&config.link_name.to_string()) + .expect("requested peer should be present"); + + for peer in config.peers { + match peer.bgp_config { + NameOrId::Id(id) => { + assert_eq!(db_peer.bgp_config_id, id); + } + NameOrId::Name(name) => { + let db_bgp_config = datastore + .bgp_config_get( + opctx, + &NameOrId::Id(db_peer.bgp_config_id), + ) + .await + .expect("bgp config should be present in db"); + + assert_eq!( + db_bgp_config.identity.name, + nexus_db_model::Name(name) + ); + } + } + + assert_eq!(db_peer.addr.ip(), peer.addr); + assert_eq!(*db_peer.hold_time, peer.hold_time); + assert_eq!(*db_peer.idle_hold_time, peer.idle_hold_time); + assert_eq!(*db_peer.delay_open, peer.delay_open); + assert_eq!(*db_peer.connect_retry, peer.connect_retry); + assert_eq!(*db_peer.keepalive, peer.keepalive); + assert_eq!(db_peer.remote_asn.map(|asn| *asn), peer.remote_asn); + assert_eq!(db_peer.min_ttl.map(|ttl| *ttl), peer.min_ttl); + assert_eq!(db_peer.md5_auth_key, peer.md5_auth_key); + assert_eq!( + db_peer.multi_exit_discriminator.map(|med| *med), + peer.multi_exit_discriminator + ); + assert_eq!(db_peer.communities, peer.communities); + assert_eq!(db_peer.local_pref.map(|lp| *lp), peer.local_pref); + assert_eq!(db_peer.enforce_first_as, peer.enforce_first_as); + assert_eq!(db_peer.allowed_export, peer.allowed_export); + assert_eq!(db_peer.allowed_import, peer.allowed_import); + assert_eq!(db_peer.vlan_id.map(|vid| *vid), peer.vlan_id); + } + } + db.terminate().await; logctx.cleanup_successful(); } diff --git a/nexus/db-schema/src/schema.rs b/nexus/db-schema/src/schema.rs index 9875d3bc395..f6e8675e4ac 100644 --- a/nexus/db-schema/src/schema.rs +++ b/nexus/db-schema/src/schema.rs @@ -2270,6 +2270,12 @@ allow_tables_to_appear_in_same_query!( bgp_config ); +allow_tables_to_appear_in_same_query!( + address_lot, + address_lot_block, + switch_port_settings, +); + allow_tables_to_appear_in_same_query!(disk, virtual_provisioning_resource); allow_tables_to_appear_in_same_query!(volume, virtual_provisioning_resource); diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index 7f2691e3e41..710ecc25a40 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -1669,7 +1669,7 @@ pub trait NexusExternalApi { async fn networking_switch_port_settings_create( rqctx: RequestContext, new_settings: TypedBody, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /// Delete switch port settings #[endpoint { @@ -1693,7 +1693,10 @@ pub trait NexusExternalApi { query_params: Query< PaginatedByNameOrId, >, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Get information about switch port #[endpoint { @@ -1704,7 +1707,7 @@ pub trait NexusExternalApi { async fn networking_switch_port_settings_view( rqctx: RequestContext, path_params: Path, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /// List switch ports #[endpoint { @@ -1881,7 +1884,7 @@ pub trait NexusExternalApi { async fn networking_bgp_announce_set_update( rqctx: RequestContext, config: TypedBody, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /// List BGP announce sets #[endpoint { diff --git a/nexus/src/app/background/tasks/networking.rs b/nexus/src/app/background/tasks/networking.rs index 5cf7d737b53..ff5ae94431c 100644 --- a/nexus/src/app/background/tasks/networking.rs +++ b/nexus/src/app/background/tasks/networking.rs @@ -63,7 +63,7 @@ pub(crate) fn api_to_dpd_port_settings( //TODO breakouts let link_id = LinkId(0); - let tx_eq = if let Some(Some(t)) = settings.tx_eq.get(0) { + let tx_eq = if let Some(t) = settings.tx_eq.get(0) { Some(TxEq { pre1: t.pre1, pre2: t.pre2, @@ -104,7 +104,7 @@ pub(crate) fn api_to_dpd_port_settings( addrs: settings .addresses .iter() - .map(|a| a.address.ip()) + .map(|a| a.address.addr()) .collect(), }, ); diff --git a/nexus/src/app/background/tasks/sync_switch_configuration.rs b/nexus/src/app/background/tasks/sync_switch_configuration.rs index 0000e4c0d27..1a10c176875 100644 --- a/nexus/src/app/background/tasks/sync_switch_configuration.rs +++ b/nexus/src/app/background/tasks/sync_switch_configuration.rs @@ -929,24 +929,24 @@ impl BackgroundTask for SwitchPortSettingsManager { }, }; - // TODO https://github.com/oxidecomputer/omicron/issues/3062 - let tx_eq = if let Some(Some(c)) = info.tx_eq.get(0) { - Some(TxEqConfig { - pre1: c.pre1, - pre2: c.pre2, - main: c.main, - post2: c.post2, - post1: c.post1, - }) - } else { - None - }; + // TODO https://github.com/oxidecomputer/omicron/issues/3062 + let tx_eq = if let Some(c) = info.tx_eq.get(0) { + Some(TxEqConfig { + pre1: c.pre1, + pre2: c.pre2, + main: c.main, + post2: c.post2, + post1: c.post1, + }) + } else { + None + }; let mut port_config = PortConfigV2 { addresses: info.addresses.iter().map(|a| UplinkAddressConfig { - address: a.address.into(), - vlan_id: a.vlan_id.map(|v| v.into()) + address: a.address, + vlan_id: a.vlan_id }).collect(), autoneg: info .links @@ -1555,7 +1555,7 @@ fn uplinks( }) }; - let tx_eq = if let Some(Some(c)) = config.tx_eq.get(0) { + let tx_eq = if let Some(c) = config.tx_eq.get(0) { Some(TxEqConfig { pre1: c.pre1, pre2: c.pre2, @@ -1573,8 +1573,8 @@ fn uplinks( .addresses .iter() .map(|a| UplinkAddressConfig { - address: a.address.into(), - vlan_id: a.vlan_id.map(|v| v.into()), + address: a.address, + vlan_id: a.vlan_id, }) .collect(), lldp, diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index 3db5e46f967..a2ef92810ee 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -569,11 +569,11 @@ impl super::Nexus { identity, port_config, groups: vec![], - links: HashMap::new(), - interfaces: HashMap::new(), - routes: HashMap::new(), - bgp_peers: HashMap::new(), - addresses: HashMap::new(), + links: vec![], + interfaces: vec![], + routes: vec![], + bgp_peers: vec![], + addresses: vec![], }; let addresses: Vec
= uplink_config @@ -586,9 +586,13 @@ impl super::Nexus { }) .collect(); - port_settings_params - .addresses - .insert("phy0".to_string(), AddressConfig { addresses }); + let link_name = + Name::from_str("phy0").expect("interface name should be valid"); + + port_settings_params.addresses.push(AddressConfig { + link_name: link_name.clone(), + addresses, + }); let routes: Vec = uplink_config .routes @@ -603,7 +607,7 @@ impl super::Nexus { port_settings_params .routes - .insert("phy0".to_string(), RouteConfig { routes }); + .push(RouteConfig { link_name: link_name.clone(), routes }); let peers: Vec = uplink_config .bgp_peers @@ -612,7 +616,7 @@ impl super::Nexus { bgp_config: NameOrId::Name( format!("as{}", r.asn).parse().unwrap(), ), - interface_name: "phy0".into(), + interface_name: link_name.to_string(), addr: r.addr.into(), hold_time: r.hold_time() as u32, idle_hold_time: r.idle_hold_time() as u32, @@ -634,7 +638,7 @@ impl super::Nexus { port_settings_params .bgp_peers - .insert("phy0".to_string(), BgpPeerConfig { peers }); + .push(BgpPeerConfig { link_name: link_name.clone(), peers }); let lldp = match &uplink_config.lldp { None => LldpLinkConfigCreate { @@ -656,6 +660,7 @@ impl super::Nexus { }; let link = LinkConfigCreate { + link_name: link_name.clone(), //TODO https://github.com/oxidecomputer/omicron/issues/2274 mtu: 1500, fec: uplink_config.uplink_port_fec.map(|fec| fec.into()), @@ -665,7 +670,7 @@ impl super::Nexus { tx_eq: uplink_config.tx_eq.map(|t| t.into()), }; - port_settings_params.links.insert("phy".to_string(), link); + port_settings_params.links.push(link); match self .db_datastore diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 129397f8269..2c470306b20 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -59,7 +59,7 @@ impl super::Nexus { fn switch_port_settings_validate( params: ¶ms::SwitchPortSettingsCreate, ) -> CreateResult<()> { - for x in params.bgp_peers.values() { + for x in ¶ms.bgp_peers { for p in x.peers.iter() { if let Some(ref key) = p.md5_auth_key { if key.len() > 80 { diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index bf1b7996f5c..125b3062105 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -82,7 +82,7 @@ use omicron_common::api::external::RouterRoute; use omicron_common::api::external::RouterRouteKind; use omicron_common::api::external::SwitchPort; use omicron_common::api::external::SwitchPortSettings; -use omicron_common::api::external::SwitchPortSettingsView; +use omicron_common::api::external::SwitchPortSettingsIdentity; use omicron_common::api::external::TufRepoGetResponse; use omicron_common::api::external::TufRepoInsertResponse; use omicron_common::api::external::VpcFirewallRuleUpdateParams; @@ -3591,7 +3591,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_settings_create( rqctx: RequestContext, new_settings: TypedBody, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -3601,7 +3601,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let result = nexus.switch_port_settings_post(&opctx, params).await?; - let settings: SwitchPortSettingsView = result.into(); + let settings: SwitchPortSettings = result.into(); Ok(HttpResponseCreated(settings)) }; apictx @@ -3636,8 +3636,10 @@ impl NexusExternalApi for NexusExternalApiImpl { query_params: Query< PaginatedByNameOrId, >, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -3670,7 +3672,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_settings_view( rqctx: RequestContext, path_params: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4048,7 +4050,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_bgp_announce_set_update( rqctx: RequestContext, config: TypedBody, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4056,7 +4058,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let result = nexus.bgp_update_announce_set(&opctx, &config).await?; - Ok(HttpResponseCreated::(result.0.into())) + Ok(HttpResponseOk::(result.0.into())) }; apictx .context diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index f54ea64b328..05d296da5df 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -4,6 +4,8 @@ //! Integration tests for operating on Ports +use std::str::FromStr; + use http::StatusCode; use http::method::Method; use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; @@ -16,19 +18,15 @@ use nexus_types::external_api::params::{ SwitchPortSettingsCreate, }; use nexus_types::external_api::views::Rack; -use omicron_common::api::external::ImportExportPolicy; use omicron_common::api::external::{ self, AddressLotKind, BgpPeer, IdentityMetadataCreateParams, LinkFec, - LinkSpeed, NameOrId, SwitchPort, SwitchPortSettingsView, + LinkSpeed, NameOrId, SwitchPort, SwitchPortSettings, }; +use omicron_common::api::external::{ImportExportPolicy, Name}; type ControlPlaneTestContext = nexus_test_utils::ControlPlaneTestContext; -// TODO: unfortunately this test can no longer be run in the integration test -// suite because it depends on communicating with MGS which is not part -// of the infrastructure available in the integration test context. -#[ignore] #[nexus_test] async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { let client = &ctx.external_client; @@ -74,10 +72,10 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { }], }; - NexusRequest::objects_post( + NexusRequest::object_put( client, "/v1/system/networking/bgp-announce-set", - &announce_set, + Some(&announce_set), ) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -113,59 +111,57 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { name: "portofino".parse().unwrap(), description: "just a port".into(), }); + + let link_name = + Name::from_str("phy0").expect("phy0 should be a valid name"); + + let lldp_params = LldpLinkConfigCreate { + enabled: true, + link_name: Some("Link Name".into()), + link_description: Some("link_ Dscription".into()), + chassis_id: Some("Chassis ID".into()), + system_name: Some("System Name".into()), + system_description: Some("System description".into()), + management_ip: None, + }; + // links - settings.links.insert( - "phy0".into(), - LinkConfigCreate { - mtu: 4700, - lldp: LldpLinkConfigCreate { - enabled: true, - link_name: Some("Link Name".into()), - link_description: Some("link_ Dscription".into()), - chassis_id: Some("Chassis ID".into()), - system_name: Some("System Name".into()), - system_description: Some("System description".into()), - management_ip: None, - }, - fec: Some(LinkFec::None), - speed: LinkSpeed::Speed100G, - autoneg: false, - tx_eq: None, - }, - ); + settings.links.push(LinkConfigCreate { + link_name: link_name.clone(), + mtu: 4700, + lldp: lldp_params.clone(), + fec: Some(LinkFec::None), + speed: LinkSpeed::Speed100G, + autoneg: false, + tx_eq: None, + }); // interfaces - settings.interfaces.insert( - "phy0".into(), - SwitchInterfaceConfigCreate { - v6_enabled: true, - kind: SwitchInterfaceKind::Primary, - }, - ); + settings.interfaces.push(SwitchInterfaceConfigCreate { + link_name: link_name.clone(), + v6_enabled: true, + kind: SwitchInterfaceKind::Primary, + }); // routes - settings.routes.insert( - "phy0".into(), - RouteConfig { - routes: vec![Route { - dst: "1.2.3.0/24".parse().unwrap(), - gw: "1.2.3.4".parse().unwrap(), - vid: None, - rib_priority: None, - }], - }, - ); + settings.routes.push(RouteConfig { + link_name: link_name.clone(), + routes: vec![Route { + dst: "1.2.3.0/24".parse().unwrap(), + gw: "1.2.3.4".parse().unwrap(), + vid: None, + rib_priority: None, + }], + }); // addresses - settings.addresses.insert( - "phy0".into(), - AddressConfig { - addresses: vec![Address { - address: "203.0.113.10/24".parse().unwrap(), - vlan_id: None, - address_lot: NameOrId::Name("parkinglot".parse().unwrap()), - }], - }, - ); + settings.addresses.push(AddressConfig { + link_name: link_name.clone(), + addresses: vec![Address { + address: "203.0.113.10/24".parse().unwrap(), + vlan_id: None, + address_lot: NameOrId::Name("parkinglot".parse().unwrap()), + }], + }); - let created: SwitchPortSettingsView = NexusRequest::objects_post( + let created: SwitchPortSettings = NexusRequest::objects_post( client, "/v1/system/networking/switch-port-settings", &settings, @@ -185,17 +181,8 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { assert_eq!(&link0.link_name, "phy0"); assert_eq!(link0.mtu, 4700); - let lldp0 = &created.link_lldp[0]; - assert_eq!(lldp0.enabled, true); - assert_eq!(lldp0.link_name, Some("Link Name".to_string())); - assert_eq!(lldp0.link_description, Some("Link Description".to_string())); - assert_eq!(lldp0.chassis_id, Some("Chassis ID".to_string())); - assert_eq!(lldp0.system_name, Some("System Name".to_string())); - assert_eq!( - lldp0.system_description, - Some("System Description".to_string()) - ); - assert_eq!(lldp0.management_ip, None); + let lldp0 = link0.lldp_link_config.clone().unwrap(); + assert_eq!(lldp0, lldp_params); let ifx0 = &created.interfaces[0]; assert_eq!(&ifx0.interface_name, "phy0"); @@ -204,13 +191,13 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { let route0 = &created.routes[0]; assert_eq!(route0.dst, "1.2.3.0/24".parse().unwrap()); - assert_eq!(route0.gw, "1.2.3.4".parse().unwrap()); + assert_eq!(&route0.gw.to_string(), "1.2.3.4"); let addr0 = &created.addresses[0]; assert_eq!(addr0.address, "203.0.113.10/24".parse().unwrap()); // Get the port settings back - let roundtrip: SwitchPortSettingsView = NexusRequest::object_get( + let roundtrip: SwitchPortSettings = NexusRequest::object_get( client, "/v1/system/networking/switch-port-settings/portofino", ) @@ -229,17 +216,8 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { assert_eq!(&link0.link_name, "phy0"); assert_eq!(link0.mtu, 4700); - let lldp0 = &roundtrip.link_lldp[0]; - assert_eq!(lldp0.enabled, true); - assert_eq!(lldp0.link_name, Some("Link Name".to_string())); - assert_eq!(lldp0.link_description, Some("Link Description".to_string())); - assert_eq!(lldp0.chassis_id, Some("Chassis ID".to_string())); - assert_eq!(lldp0.system_name, Some("System Name".to_string())); - assert_eq!( - lldp0.system_description, - Some("System Description".to_string()) - ); - assert_eq!(lldp0.management_ip, None); + let lldp0 = link0.lldp_link_config.clone().unwrap(); + assert_eq!(lldp0, lldp_params); let ifx0 = &roundtrip.interfaces[0]; assert_eq!(&ifx0.interface_name, "phy0"); @@ -248,7 +226,7 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { let route0 = &roundtrip.routes[0]; assert_eq!(route0.dst, "1.2.3.0/24".parse().unwrap()); - assert_eq!(route0.gw, "1.2.3.4".parse().unwrap()); + assert_eq!(&route0.gw.to_string(), "1.2.3.4"); let addr0 = &roundtrip.addresses[0]; assert_eq!(addr0.address, "203.0.113.10/24".parse().unwrap()); @@ -267,7 +245,7 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { .unwrap(); // Create same port settings again. Should not see conflict. - let _created: SwitchPortSettingsView = NexusRequest::objects_post( + let _created: SwitchPortSettings = NexusRequest::objects_post( client, "/v1/system/networking/switch-port-settings", &settings, @@ -280,32 +258,30 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { .unwrap(); // Update port settings. Should not see conflict. - settings.bgp_peers.insert( - "phy0".into(), - BgpPeerConfig { - peers: vec![BgpPeer { - bgp_config: NameOrId::Name("as47".parse().unwrap()), - interface_name: "phy0".to_string(), - addr: "1.2.3.4".parse().unwrap(), - hold_time: 6, - idle_hold_time: 6, - delay_open: 0, - connect_retry: 3, - keepalive: 2, - remote_asn: None, - min_ttl: None, - md5_auth_key: None, - multi_exit_discriminator: None, - communities: Vec::new(), - local_pref: None, - enforce_first_as: false, - allowed_export: ImportExportPolicy::NoFiltering, - allowed_import: ImportExportPolicy::NoFiltering, - vlan_id: None, - }], - }, - ); - let _created: SwitchPortSettingsView = NexusRequest::objects_post( + settings.bgp_peers.push(BgpPeerConfig { + link_name: link_name.clone(), + peers: vec![BgpPeer { + bgp_config: NameOrId::Name("as47".parse().unwrap()), + interface_name: "phy0".to_string(), + addr: "1.2.3.4".parse().unwrap(), + hold_time: 6, + idle_hold_time: 6, + delay_open: 0, + connect_retry: 3, + keepalive: 2, + remote_asn: None, + min_ttl: None, + md5_auth_key: None, + multi_exit_discriminator: None, + communities: Vec::new(), + local_pref: None, + enforce_first_as: false, + allowed_export: ImportExportPolicy::NoFiltering, + allowed_import: ImportExportPolicy::NoFiltering, + vlan_id: None, + }], + }); + let _created: SwitchPortSettings = NexusRequest::objects_post( client, "/v1/system/networking/switch-port-settings", &settings, diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 5fb62a4ee70..518efe864b0 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -14,7 +14,7 @@ use omicron_common::api::external::{ ByteCount, FailureDomain, Hostname, IdentityMetadataCreateParams, IdentityMetadataUpdateParams, InstanceAutoRestartPolicy, InstanceCpuCount, LinkFec, LinkSpeed, Name, NameOrId, PaginationOrder, RouteDestination, - RouteTarget, TxEqConfig, UserId, + RouteTarget, UserId, }; use omicron_common::disk::DiskVariant; use oxnet::{IpNet, Ipv4Net, Ipv6Net}; @@ -27,7 +27,6 @@ use serde::{ }; use std::collections::BTreeMap; use std::collections::BTreeSet; -use std::collections::HashMap; use std::{net::IpAddr, str::FromStr}; use url::Url; use uuid::Uuid; @@ -1737,20 +1736,31 @@ pub struct SwtichPortSettingsGroupCreate { pub struct SwitchPortSettingsCreate { #[serde(flatten)] pub identity: IdentityMetadataCreateParams, + pub port_config: SwitchPortConfigCreate, + + #[serde(default)] pub groups: Vec, + /// Links indexed by phy name. On ports that are not broken out, this is /// always phy0. On a 2x breakout the options are phy0 and phy1, on 4x /// phy0-phy3, etc. - pub links: HashMap, + pub links: Vec, + /// Interfaces indexed by link name. - pub interfaces: HashMap, + #[serde(default)] + pub interfaces: Vec, + /// Routes indexed by interface name. - pub routes: HashMap, + #[serde(default)] + pub routes: Vec, + /// BGP peers indexed by interface name. - pub bgp_peers: HashMap, + #[serde(default)] + pub bgp_peers: Vec, + /// Addresses indexed by interface name. - pub addresses: HashMap, + pub addresses: Vec, } impl SwitchPortSettingsCreate { @@ -1761,11 +1771,11 @@ impl SwitchPortSettingsCreate { geometry: SwitchPortGeometry::Qsfp28x1, }, groups: Vec::new(), - links: HashMap::new(), - interfaces: HashMap::new(), - routes: HashMap::new(), - bgp_peers: HashMap::new(), - addresses: HashMap::new(), + links: Vec::new(), + interfaces: Vec::new(), + routes: Vec::new(), + bgp_peers: Vec::new(), + addresses: Vec::new(), } } } @@ -1795,6 +1805,9 @@ pub enum SwitchPortGeometry { /// Switch link configuration. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct LinkConfigCreate { + /// Link name + pub link_name: Name, + /// Maximum transmission unit for the link. pub mtu: u16, @@ -1816,6 +1829,36 @@ pub struct LinkConfigCreate { pub tx_eq: Option, } +/// 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, +} + +impl From for TxEqConfig { + fn from( + x: omicron_common::api::internal::shared::TxEqConfig, + ) -> TxEqConfig { + TxEqConfig { + pre1: x.pre1, + pre2: x.pre2, + main: x.main, + post2: x.post2, + post1: x.post1, + } + } +} + /// The LLDP configuration associated with a port. #[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)] pub struct LldpLinkConfigCreate { @@ -1841,10 +1884,44 @@ pub struct LldpLinkConfigCreate { pub management_ip: Option, } +impl PartialEq + for omicron_common::api::external::LldpLinkConfig +{ + fn eq(&self, other: &LldpLinkConfigCreate) -> bool { + self.enabled == other.enabled + && self.link_name == other.link_name + && self.link_description == other.link_description + && self.chassis_id == other.chassis_id + && self.system_name == other.system_name + && self.system_description == other.system_description + && self.management_ip == other.management_ip + } +} + +impl PartialEq + for LldpLinkConfigCreate +{ + fn eq( + &self, + other: &omicron_common::api::external::LldpLinkConfig, + ) -> bool { + self.enabled == other.enabled + && self.link_name == other.link_name + && self.link_description == other.link_description + && self.chassis_id == other.chassis_id + && self.system_name == other.system_name + && self.system_description == other.system_description + && self.management_ip == other.management_ip + } +} + /// A layer-3 switch interface configuration. When IPv6 is enabled, a link local /// address will be created for the interface. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct SwitchInterfaceConfigCreate { + /// Link the interface will be assigned to + pub link_name: Name, + /// Whether or not IPv6 is enabled. pub v6_enabled: bool, @@ -1883,6 +1960,9 @@ pub struct SwitchVlanInterface { /// Route configuration data associated with a switch port configuration. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct RouteConfig { + /// Link the route should be active on + pub link_name: Name, + /// The set of routes assigned to a switch port. pub routes: Vec, } @@ -1913,6 +1993,9 @@ pub struct BgpConfigSelector { #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct BgpPeerConfig { + /// Link that the peer is reachable on + pub link_name: Name, + pub peers: Vec, } @@ -2028,6 +2111,9 @@ pub struct BfdSessionDisable { /// A set of addresses associated with a port configuration. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct AddressConfig { + /// Link to assign the address to + pub link_name: Name, + /// The set of addresses assigned to the port configuration. pub addresses: Vec
, } diff --git a/openapi/nexus.json b/openapi/nexus.json index 76f2e44f6cd..6bfc55e608e 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -9453,8 +9453,8 @@ "required": true }, "responses": { - "201": { - "description": "successful creation", + "200": { + "description": "successful operation", "content": { "application/json": { "schema": { @@ -9893,7 +9893,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsResultsPage" + "$ref": "#/components/schemas/SwitchPortSettingsIdentityResultsPage" } } } @@ -9931,7 +9931,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsView" + "$ref": "#/components/schemas/SwitchPortSettings" } } } @@ -9997,7 +9997,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SwitchPortSettingsView" + "$ref": "#/components/schemas/SwitchPortSettings" } } } @@ -12827,10 +12827,19 @@ "items": { "$ref": "#/components/schemas/Address" } + }, + "link_name": { + "description": "Link to assign the address to", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] } }, "required": [ - "addresses" + "addresses", + "link_name" ] }, "AddressLot": { @@ -14549,6 +14558,14 @@ "BgpPeerConfig": { "type": "object", "properties": { + "link_name": { + "description": "Link that the peer is reachable on", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, "peers": { "type": "array", "items": { @@ -14557,6 +14574,7 @@ } }, "required": [ + "link_name", "peers" ] }, @@ -20447,6 +20465,14 @@ } ] }, + "link_name": { + "description": "Link name", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, "lldp": { "description": "The link-layer discovery protocol (LLDP) configuration for the link.", "allOf": [ @@ -20481,6 +20507,7 @@ }, "required": [ "autoneg", + "link_name", "lldp", "mtu", "speed" @@ -20611,11 +20638,8 @@ "management_ip": { "nullable": true, "description": "The LLDP management IP TLV.", - "allOf": [ - { - "$ref": "#/components/schemas/IpNet" - } - ] + "type": "string", + "format": "ip" }, "system_description": { "nullable": true, @@ -21870,6 +21894,14 @@ "description": "Route configuration data associated with a switch port configuration.", "type": "object", "properties": { + "link_name": { + "description": "Link the route should be active on", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, "routes": { "description": "The set of routes assigned to a switch port.", "type": "array", @@ -21879,6 +21911,7 @@ } }, "required": [ + "link_name", "routes" ] }, @@ -23607,6 +23640,14 @@ } ] }, + "link_name": { + "description": "Link the interface will be assigned to", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, "v6_enabled": { "description": "Whether or not IPv6 is enabled.", "type": "boolean" @@ -23614,6 +23655,7 @@ }, "required": [ "kind", + "link_name", "v6_enabled" ] }, @@ -23756,7 +23798,7 @@ "switch_location" ] }, - "SwitchPortAddressConfig": { + "SwitchPortAddressView": { "description": "An IP address configuration for a port settings object.", "type": "object", "properties": { @@ -23773,6 +23815,19 @@ "type": "string", "format": "uuid" }, + "address_lot_id": { + "description": "The id of the address lot this address is drawn from.", + "type": "string", + "format": "uuid" + }, + "address_lot_name": { + "description": "The name of the address lot this address is drawn from.", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, "interface_name": { "description": "The interface name this address belongs to.", "type": "string" @@ -23793,6 +23848,8 @@ "required": [ "address", "address_lot_block_id", + "address_lot_id", + "address_lot_name", "interface_name", "port_settings_id" ] @@ -23927,11 +23984,14 @@ "description": "The name of this link.", "type": "string" }, - "lldp_link_config_id": { + "lldp_link_config": { "nullable": true, - "description": "The link-layer discovery protocol service configuration id for this link.", - "type": "string", - "format": "uuid" + "description": "The link-layer discovery protocol service configuration for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/LldpLinkConfig" + } + ] }, "mtu": { "description": "The maximum transmission unit for this link.", @@ -23952,11 +24012,14 @@ } ] }, - "tx_eq_config_id": { + "tx_eq_config": { "nullable": true, - "description": "The tx_eq configuration id for this link.", - "type": "string", - "format": "uuid" + "description": "The tx_eq configuration for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/TxEqConfig2" + } + ] } }, "required": [ @@ -24002,11 +24065,8 @@ }, "gw": { "description": "The route's gateway address.", - "allOf": [ - { - "$ref": "#/components/schemas/IpNet" - } - ] + "type": "string", + "format": "ip" }, "interface_name": { "description": "The interface name this route configuration is assigned to.", @@ -24040,18 +24100,53 @@ ] }, "SwitchPortSettings": { - "description": "A switch port settings identity whose id may be used to view additional details.", + "description": "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.", "type": "object", "properties": { + "addresses": { + "description": "Layer 3 IP address settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortAddressView" + } + }, + "bgp_peers": { + "description": "BGP peer settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpPeer" + } + }, "description": { "description": "human-readable free-form text about a resource", "type": "string" }, + "groups": { + "description": "Switch port settings included from other switch port settings groups.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortSettingsGroups" + } + }, "id": { "description": "unique, immutable, system-controlled identifier for each resource", "type": "string", "format": "uuid" }, + "interfaces": { + "description": "Layer 3 interface settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchInterfaceConfig" + } + }, + "links": { + "description": "Layer 2 link settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortLinkConfig" + } + }, "name": { "description": "unique, mutable, user-controlled identifier for each resource", "allOf": [ @@ -24060,6 +24155,21 @@ } ] }, + "port": { + "description": "Layer 1 physical port settings.", + "allOf": [ + { + "$ref": "#/components/schemas/SwitchPortConfig" + } + ] + }, + "routes": { + "description": "IP route settings.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SwitchPortRouteConfig" + } + }, "time_created": { "description": "timestamp when this resource was created", "type": "string", @@ -24069,14 +24179,29 @@ "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": [ + "addresses", + "bgp_peers", "description", + "groups", "id", + "interfaces", + "links", "name", + "port", + "routes", "time_created", - "time_modified" + "time_modified", + "vlan_interfaces" ] }, "SwitchPortSettingsCreate": { @@ -24085,15 +24210,16 @@ "properties": { "addresses": { "description": "Addresses indexed by interface name.", - "type": "object", - "additionalProperties": { + "type": "array", + "items": { "$ref": "#/components/schemas/AddressConfig" } }, "bgp_peers": { "description": "BGP peers indexed by interface name.", - "type": "object", - "additionalProperties": { + "default": [], + "type": "array", + "items": { "$ref": "#/components/schemas/BgpPeerConfig" } }, @@ -24101,6 +24227,7 @@ "type": "string" }, "groups": { + "default": [], "type": "array", "items": { "$ref": "#/components/schemas/NameOrId" @@ -24108,15 +24235,16 @@ }, "interfaces": { "description": "Interfaces indexed by link name.", - "type": "object", - "additionalProperties": { + "default": [], + "type": "array", + "items": { "$ref": "#/components/schemas/SwitchInterfaceConfigCreate" } }, "links": { "description": "Links indexed by phy name. On ports that are not broken out, this is always phy0. On a 2x breakout the options are phy0 and phy1, on 4x phy0-phy3, etc.", - "type": "object", - "additionalProperties": { + "type": "array", + "items": { "$ref": "#/components/schemas/LinkConfigCreate" } }, @@ -24128,22 +24256,19 @@ }, "routes": { "description": "Routes indexed by interface name.", - "type": "object", - "additionalProperties": { + "default": [], + "type": "array", + "items": { "$ref": "#/components/schemas/RouteConfig" } } }, "required": [ "addresses", - "bgp_peers", "description", - "groups", - "interfaces", "links", "name", - "port_config", - "routes" + "port_config" ] }, "SwitchPortSettingsGroups": { @@ -24166,128 +24291,65 @@ "port_settings_id" ] }, - "SwitchPortSettingsResultsPage": { - "description": "A single page of results", + "SwitchPortSettingsIdentity": { + "description": "A switch port settings identity whose id may be used to view additional details.", "type": "object", "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortSettings" - } - }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", + "description": { + "description": "human-readable free-form text about a resource", "type": "string" - } - }, - "required": [ - "items" - ] - }, - "SwitchPortSettingsView": { - "description": "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.", - "type": "object", - "properties": { - "addresses": { - "description": "Layer 3 IP address settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortAddressConfig" - } - }, - "bgp_peers": { - "description": "BGP peer settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/BgpPeer" - } }, - "groups": { - "description": "Switch port settings included from other switch port settings groups.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortSettingsGroups" - } - }, - "interfaces": { - "description": "Layer 3 interface settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchInterfaceConfig" - } - }, - "link_lldp": { - "description": "Link-layer discovery protocol (LLDP) settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/LldpLinkConfig" - } - }, - "links": { - "description": "Layer 2 link settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortLinkConfig" - } + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" }, - "port": { - "description": "Layer 1 physical port settings.", + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", "allOf": [ { - "$ref": "#/components/schemas/SwitchPortConfig" + "$ref": "#/components/schemas/Name" } ] }, - "routes": { - "description": "IP route settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchPortRouteConfig" - } - }, - "settings": { - "description": "The primary switch port settings handle.", - "allOf": [ - { - "$ref": "#/components/schemas/SwitchPortSettings" - } - ] + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" }, - "tx_eq": { - "description": "TX equalization settings. These are optional, and most links will not need them.", + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "description", + "id", + "name", + "time_created", + "time_modified" + ] + }, + "SwitchPortSettingsIdentityResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", "type": "array", "items": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/TxEqConfig" - } - ] + "$ref": "#/components/schemas/SwitchPortSettingsIdentity" } }, - "vlan_interfaces": { - "description": "Vlan interface settings.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SwitchVlanInterfaceConfig" - } + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" } }, "required": [ - "addresses", - "bgp_peers", - "groups", - "interfaces", - "link_lldp", - "links", - "port", - "routes", - "settings", - "tx_eq", - "vlan_interfaces" + "items" ] }, "SwitchResultsPage": { @@ -24578,6 +24640,42 @@ } } }, + "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" + } + } + }, "TypedUuidForAlertKind": { "type": "string", "format": "uuid"