diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index ac9c8abc01..6bd4e61f70 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -305,39 +305,41 @@ impl DataStore { .await?; let mut bgp_peer_config = Vec::new(); - for (interface_name, p) in ¶ms.bgp_peers { - use db::schema::bgp_config; - let bgp_config_id = match &p.bgp_config { - NameOrId::Id(id) => *id, - NameOrId::Name(name) => { - let name = name.to_string(); - bgp_config_dsl::bgp_config - .filter(bgp_config::time_deleted.is_null()) - .filter(bgp_config::name.eq(name)) - .select(bgp_config::id) - .limit(1) - .first_async::(&conn) - .await - .map_err(|_| - TxnError::CustomError( - SwitchPortSettingsCreateError::BgpConfigNotFound, - ) - )? - } - }; + for (interface_name, peer_config) in ¶ms.bgp_peers { + for p in &peer_config.peers { + use db::schema::bgp_config; + let bgp_config_id = match &p.bgp_config { + NameOrId::Id(id) => *id, + NameOrId::Name(name) => { + let name = name.to_string(); + bgp_config_dsl::bgp_config + .filter(bgp_config::time_deleted.is_null()) + .filter(bgp_config::name.eq(name)) + .select(bgp_config::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|_| + TxnError::CustomError( + SwitchPortSettingsCreateError::BgpConfigNotFound, + ) + )? + } + }; - bgp_peer_config.push(SwitchPortBgpPeerConfig::new( - psid, - bgp_config_id, - interface_name.clone(), - p.addr.into(), - p.hold_time.into(), - p.idle_hold_time.into(), - p.delay_open.into(), - p.connect_retry.into(), - p.keepalive.into(), - )); + bgp_peer_config.push(SwitchPortBgpPeerConfig::new( + psid, + bgp_config_id, + interface_name.clone(), + p.addr.into(), + p.hold_time.into(), + p.idle_hold_time.into(), + p.delay_open.into(), + p.connect_retry.into(), + p.keepalive.into(), + )); + } } result.bgp_peers = diesel::insert_into( @@ -1153,8 +1155,8 @@ mod test { use crate::db::datastore::{datastore_test, UpdatePrecondition}; use nexus_test_utils::db::test_setup_database; use nexus_types::external_api::params::{ - BgpAnnounceSetCreate, BgpConfigCreate, BgpPeerConfig, SwitchPortConfig, - SwitchPortGeometry, SwitchPortSettingsCreate, + BgpAnnounceSetCreate, BgpConfigCreate, BgpPeer, BgpPeerConfig, + SwitchPortConfig, SwitchPortGeometry, SwitchPortSettingsCreate, }; use omicron_common::api::external::{ IdentityMetadataCreateParams, Name, NameOrId, @@ -1218,19 +1220,21 @@ mod test { bgp_peers: HashMap::from([( "phy0".into(), BgpPeerConfig { - bgp_announce_set: NameOrId::Name( - "test-announce-set".parse().unwrap(), - ), - 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, + peers: vec![BgpPeer { + bgp_announce_set: NameOrId::Name( + "test-announce-set".parse().unwrap(), + ), + 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, + }], }, )]), addresses: HashMap::new(), diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index 6830af944e..fef3cb7252 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -23,12 +23,16 @@ use nexus_db_queries::db::lookup::LookupPath; use nexus_types::external_api::params::Address; use nexus_types::external_api::params::AddressConfig; use nexus_types::external_api::params::AddressLotBlockCreate; +use nexus_types::external_api::params::BgpAnnounceSetCreate; +use nexus_types::external_api::params::BgpAnnouncementCreate; +use nexus_types::external_api::params::BgpConfigCreate; +use nexus_types::external_api::params::BgpPeer; use nexus_types::external_api::params::LinkConfig; use nexus_types::external_api::params::LldpServiceConfig; use nexus_types::external_api::params::RouteConfig; use nexus_types::external_api::params::SwitchPortConfig; use nexus_types::external_api::params::{ - AddressLotCreate, LoopbackAddressCreate, Route, SiloCreate, + AddressLotCreate, BgpPeerConfig, LoopbackAddressCreate, Route, SiloCreate, SwitchPortSettingsCreate, }; use nexus_types::external_api::shared::Baseboard; @@ -53,8 +57,8 @@ use sled_agent_client::types::EarlyNetworkConfigBody; use sled_agent_client::types::StartSledAgentRequest; use sled_agent_client::types::StartSledAgentRequestBody; use sled_agent_client::types::{ - BgpConfig, BgpPeerConfig, EarlyNetworkConfig, PortConfigV1, - RackNetworkConfigV1, RouteConfig as SledRouteConfig, + BgpConfig, BgpPeerConfig as SledBgpPeerConfig, EarlyNetworkConfig, + PortConfigV1, RackNetworkConfigV1, RouteConfig as SledRouteConfig, }; use std::collections::BTreeMap; use std::collections::BTreeSet; @@ -408,6 +412,108 @@ impl super::Nexus { Error::internal_error(&format!("unable to retrieve authz_address_lot for infra address_lot: {e}")) })?; + let mut bgp_configs = HashMap::new(); + + for bgp_config in &rack_network_config.bgp { + bgp_configs.insert(bgp_config.asn, bgp_config.clone()); + + let bgp_config_name: Name = + format!("as{}", bgp_config.asn).parse().unwrap(); + + let announce_set_name: Name = + format!("as{}-announce", bgp_config.asn).parse().unwrap(); + + let address_lot_name: Name = + format!("as{}-lot", bgp_config.asn).parse().unwrap(); + + self.db_datastore + .address_lot_create( + &opctx, + &AddressLotCreate { + identity: IdentityMetadataCreateParams { + name: address_lot_name, + description: format!( + "Address lot for announce set in as {}", + bgp_config.asn + ), + }, + kind: AddressLotKind::Infra, + blocks: bgp_config + .originate + .iter() + .map(|o| AddressLotBlockCreate { + first_address: o.network().into(), + last_address: o.broadcast().into(), + }) + .collect(), + }, + ) + .await + .map_err(|e| { + Error::internal_error(&format!( + "unable to create address lot for BGP as {}: {}", + bgp_config.asn, e + )) + })?; + + self.db_datastore + .bgp_create_announce_set( + &opctx, + &BgpAnnounceSetCreate { + identity: IdentityMetadataCreateParams { + name: announce_set_name.clone(), + description: format!( + "Announce set for AS {}", + bgp_config.asn + ), + }, + announcement: bgp_config + .originate + .iter() + .map(|x| BgpAnnouncementCreate { + address_lot_block: NameOrId::Name( + format!("as{}", bgp_config.asn) + .parse() + .unwrap(), + ), + network: IpNetwork::from(*x).into(), + }) + .collect(), + }, + ) + .await + .map_err(|e| { + Error::internal_error(&format!( + "unable to create bgp announce set for as {}: {}", + bgp_config.asn, e + )) + })?; + + self.db_datastore + .bgp_config_set( + &opctx, + &BgpConfigCreate { + identity: IdentityMetadataCreateParams { + name: bgp_config_name, + description: format!( + "BGP config for AS {}", + bgp_config.asn + ), + }, + asn: bgp_config.asn, + bgp_announce_set_id: announce_set_name.into(), + vrf: None, + }, + ) + .await + .map_err(|e| { + Error::internal_error(&format!( + "unable to set bgp config for as {}: {}", + bgp_config.asn, e + )) + })?; + } + for (idx, uplink_config) in rack_network_config.ports.iter().enumerate() { @@ -505,6 +611,30 @@ impl super::Nexus { .routes .insert("phy0".to_string(), RouteConfig { routes }); + let peers: Vec = uplink_config + .bgp_peers + .iter() + .map(|r| BgpPeer { + bgp_announce_set: NameOrId::Name( + format!("as{}-announce", r.asn).parse().unwrap(), //TODO unwrap + ), + bgp_config: NameOrId::Name( + format!("as{}", r.asn).parse().unwrap(), //TODO unwrap + ), + interface_name: "phy0".into(), + addr: r.addr.into(), + hold_time: r.hold_time.unwrap_or(6) as u32, + idle_hold_time: r.idle_hold_time.unwrap_or(3) as u32, + delay_open: r.delay_open.unwrap_or(0) as u32, + connect_retry: r.connect_retry.unwrap_or(3) as u32, + keepalive: r.keepalive.unwrap_or(2) as u32, + }) + .collect(); + + port_settings_params + .bgp_peers + .insert("phy0".to_string(), BgpPeerConfig { peers }); + let link = LinkConfig { mtu: 1500, //TODO as parameter lldp: LldpServiceConfig { @@ -673,7 +803,7 @@ impl super::Nexus { addresses: info.addresses.iter().map(|a| a.address).collect(), bgp_peers: peer_info .iter() - .map(|(p, asn, addr)| BgpPeerConfig { + .map(|(p, asn, addr)| SledBgpPeerConfig { addr: *addr, asn: *asn, port: port.port_name.clone(), diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index b3af9fb6a8..10e9061dba 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -10,7 +10,7 @@ use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::params::{ Address, AddressConfig, AddressLotBlockCreate, AddressLotCreate, - BgpAnnounceSetCreate, BgpAnnouncementCreate, BgpConfigCreate, + BgpAnnounceSetCreate, BgpAnnouncementCreate, BgpConfigCreate, BgpPeer, BgpPeerConfig, LinkConfig, LinkFec, LinkSpeed, LldpServiceConfig, Route, RouteConfig, SwitchInterfaceConfig, SwitchInterfaceKind, SwitchPortApplySettings, SwitchPortSettingsCreate, @@ -253,15 +253,17 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { settings.bgp_peers.insert( "phy0".into(), BgpPeerConfig { - bgp_config: NameOrId::Name("as47".parse().unwrap()), //TODO - bgp_announce_set: NameOrId::Name("instances".parse().unwrap()), //TODO - 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, + peers: vec![BgpPeer { + bgp_config: NameOrId::Name("as47".parse().unwrap()), //TODO + bgp_announce_set: NameOrId::Name("instances".parse().unwrap()), //TODO + 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, + }], }, ); let _created: SwitchPortSettingsView = NexusRequest::objects_post( diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 37b400afa9..9b11b0f524 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -1511,12 +1511,17 @@ pub struct BgpConfigListSelector { pub name_or_id: Option, } +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpPeerConfig { + pub peers: Vec, +} + /// A BGP peer configuration for an interface. Includes the set of announcements /// that will be advertised to the peer identified by `addr`. The `bgp_config` /// parameter is a reference to global BGP parameters. The `interface_name` /// indicates what interface the peer should be contacted on. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct BgpPeerConfig { +pub struct BgpPeer { /// The set of announcements advertised by the peer. pub bgp_announce_set: NameOrId, diff --git a/openapi/nexus.json b/openapi/nexus.json index a25b091742..fa0e22f579 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7816,7 +7816,7 @@ "switch" ] }, - "BgpPeerConfig": { + "BgpPeer": { "description": "A BGP peer configuration for an interface. Includes the set of announcements that will be advertised to the peer identified by `addr`. The `bgp_config` parameter is a reference to global BGP parameters. The `interface_name` indicates what interface the peer should be contacted on.", "type": "object", "properties": { @@ -7888,6 +7888,20 @@ "keepalive" ] }, + "BgpPeerConfig": { + "type": "object", + "properties": { + "peers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpPeer" + } + } + }, + "required": [ + "peers" + ] + }, "BgpPeerState": { "description": "The current state of a BGP peer.", "oneOf": [