Skip to content

Commit

Permalink
Add vlan support to uplink configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
Nieuwejaar committed May 28, 2024
1 parent acbb88e commit ac86b0d
Show file tree
Hide file tree
Showing 36 changed files with 299 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .github/buildomat/jobs/a4x2-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#: "%/out/dhcp-server.log",
#: ]
#: skip_clone = true
#: enable = false
#: enable = true
#:
#: [dependencies.a4x2]
#: job = "a4x2-prepare"
Expand Down
2 changes: 1 addition & 1 deletion .github/buildomat/jobs/a4x2-prepare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#: access_repos = [
#: "oxidecomputer/testbed",
#: ]
#: enable = false
#: enable = true

source ./env.sh

Expand Down
2 changes: 1 addition & 1 deletion .github/buildomat/jobs/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ infra_ip_last = \"$UPLINK_IP\"
/^routes/c\\
routes = \\[{nexthop = \"$GATEWAY_IP\", destination = \"0.0.0.0/0\"}\\]
/^addresses/c\\
addresses = \\[\"$UPLINK_IP/24\"\\]
addresses = \\[{address = \"$UPLINK_IP/24\"} \\]
}
" pkg/config-rss.toml
diff -u pkg/config-rss.toml{~,} || true
Expand Down
1 change: 1 addition & 0 deletions clients/sled-agent-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ progenitor::generate_api!(
BgpPeerConfig = { derives = [PartialEq, Eq, Hash, Serialize, Deserialize] },
PortConfigV1 = { derives = [PartialEq, Eq, Hash, Serialize, Deserialize] },
RouteConfig = { derives = [PartialEq, Eq, Hash, Serialize, Deserialize] },
UplinkAddressConfig = { derives = [PartialEq, Eq, Hash, Serialize, Deserialize] },
IpNet = { derives = [PartialEq, Eq, Hash, Serialize, Deserialize] },
},
//TODO trade the manual transformations later in this file for the
Expand Down
3 changes: 3 additions & 0 deletions common/src/api/external/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2897,6 +2897,9 @@ pub struct SwitchPortAddressConfig {
/// The IP address and prefix.
pub address: IpNet,

/// An optional VLAN ID
pub vlan_id: Option<u16>,

/// The interface name this address belongs to.
// TODO: https://github.com/oxidecomputer/omicron/issues/3050
// Use `Name` instead of `String` for `interface_name` type
Expand Down
84 changes: 78 additions & 6 deletions common/src/api/internal/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,81 @@ pub struct RouteConfig {
pub vlan_id: Option<u16>,
}

#[derive(
Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Hash,
)]
pub struct UplinkAddressConfig {
pub address: IpNetwork,
/// The VLAN id associated with this route.
#[serde(default)]
pub vlan_id: Option<u16>,
}

impl UplinkAddressConfig {
pub fn ip(&self) -> IpAddr {
self.address.ip()
}
}

impl std::fmt::Display for UplinkAddressConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.vlan_id {
None => write!(f, "{}", self.address),
Some(v) => write!(f, "{};{}", self.address, v),
}
}
}

#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct UplinkAddressConfigError(String);

impl std::fmt::Display for UplinkAddressConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "parse switch location error: {}", self.0)
}
}

/// Convert a string into an UplinkAddressConfig.
/// 192.168.1.1/24 => UplinkAddressConfig { 192.168.1.1/24, None }
/// 192.168.1.1/24;200 => UplinkAddressConfig { 192.168.1.1/24, Some(200) }
impl FromStr for UplinkAddressConfig {
type Err = UplinkAddressConfigError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let fields: Vec<&str> = s.split(';').collect();
let (address, vlan_id) = match fields.len() {
1 => Ok((fields[0], None)),
2 => Ok((fields[0], Some(fields[1]))),
_ => Err(UplinkAddressConfigError(format!(
"not a valid uplink address: {s}"
))),
}?;
let address = address.parse().map_err(|_| {
UplinkAddressConfigError(format!(
"not a valid ip address: {address}"
))
})?;
let vlan_id = match vlan_id {
None => Ok(None),
Some(v) => match v.parse() {
Err(_) => Err(format!("invalid vlan id: {v}")),
Ok(vlan_id) if vlan_id > 1 && vlan_id < 4096 => {
Ok(Some(vlan_id))
}
Ok(vlan_id) => Err(format!("vlan id out of range: {vlan_id}")),
},
}
.map_err(|e| UplinkAddressConfigError(e))?;
Ok(UplinkAddressConfig { address, vlan_id })
}
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct PortConfigV1 {
/// The set of routes associated with this port.
pub routes: Vec<RouteConfig>,
/// This port's addresses.
pub addresses: Vec<IpNetwork>,
/// This port's addresses and optional vlan IDs
pub addresses: Vec<UplinkAddressConfig>,
/// Switch the port belongs to.
pub switch: SwitchLocation,
/// Nmae of the port this config applies to.
Expand All @@ -326,9 +395,12 @@ impl From<UplinkConfig> for PortConfigV1 {
routes: vec![RouteConfig {
destination: "0.0.0.0/0".parse().unwrap(),
nexthop: value.gateway_ip.into(),
vlan_id: None,
vlan_id: value.uplink_vid,
}],
addresses: vec![UplinkAddressConfig {
address: value.uplink_cidr.into(),
vlan_id: value.uplink_vid,
}],
addresses: vec![value.uplink_cidr.into()],
switch: value.switch,
port: value.uplink_port,
uplink_port_speed: value.uplink_port_speed,
Expand Down Expand Up @@ -372,8 +444,8 @@ pub struct HostPortConfig {
pub port: String,

/// IP Address and prefix (e.g., `192.168.0.1/16`) to apply to switchport
/// (must be in infra_ip pool)
pub addrs: Vec<IpNetwork>,
/// (must be in infra_ip pool). May also include an optional VLAN ID.
pub addrs: Vec<UplinkAddressConfig>,
}

impl From<PortConfigV1> for HostPortConfig {
Expand Down
2 changes: 1 addition & 1 deletion dev-tools/xtask/src/virtual_hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const ZPOOL: &'static str = "/usr/sbin/zpool";
const ZONEADM: &'static str = "/usr/sbin/zoneadm";

const SIDECAR_LITE_COMMIT: &'static str =
"960f11afe859e0316088e04578aedb700fba6159";
"25a024af758371b5bfb655e58f4b7bb3e6968d7b";
const SOFTNPU_COMMIT: &'static str = "3203c51cf4473d30991b522062ac0df2e045c2f2";
const PXA_MAC_DEFAULT: &'static str = "a8:e1:de:01:70:1d";

Expand Down
2 changes: 1 addition & 1 deletion docs/how-to-run.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ routes = [{nexthop = "192.168.1.199", destination = "0.0.0.0/0"}]
# Addresses associated with this port.
# For softnpu, an address within the "infra" block above that will be used for
# the softnpu uplink port. You can just pick the first address in that pool.
addresses = ["192.168.1.30/24"]
addresses = [{address = "192.168.1.30/24"}]
# Name of the uplink port. This should always be "qsfp0" when using softnpu.
port = "qsfp0"
# The speed of this port.
Expand Down
1 change: 1 addition & 0 deletions nexus/db-model/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ table! {
rsvd_address_lot_block_id -> Uuid,
address -> Inet,
interface_name -> Text,
vlan_id -> Nullable<Int4>,
}
}

Expand Down
4 changes: 4 additions & 0 deletions nexus/db-model/src/switch_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ pub struct SwitchPortAddressConfig {
pub rsvd_address_lot_block_id: Uuid,
pub address: IpNetwork,
pub interface_name: String,
pub vlan_id: Option<SqlU16>,
}

impl SwitchPortAddressConfig {
Expand All @@ -731,13 +732,15 @@ impl SwitchPortAddressConfig {
rsvd_address_lot_block_id: Uuid,
address: IpNetwork,
interface_name: String,
vlan_id: Option<u16>,
) -> Self {
Self {
port_settings_id,
address_lot_block_id,
rsvd_address_lot_block_id,
address,
interface_name,
vlan_id: vlan_id.map(|x| x.into()),
}
}
}
Expand All @@ -749,6 +752,7 @@ impl Into<external::SwitchPortAddressConfig> for SwitchPortAddressConfig {
address_lot_block_id: self.address_lot_block_id,
address: self.address.into(),
interface_name: self.interface_name,
vlan_id: self.vlan_id.map(|x| x.into()),
}
}
}
1 change: 1 addition & 0 deletions nexus/db-queries/src/db/datastore/switch_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ impl DataStore {
rsvd_block.id,
address.address.into(),
interface_name.clone(),
address.vlan_id
));

}
Expand Down
12 changes: 10 additions & 2 deletions nexus/src/app/background/sync_switch_configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ use sled_agent_client::types::{
BgpConfig as SledBgpConfig, BgpPeerConfig as SledBgpPeerConfig,
EarlyNetworkConfig, EarlyNetworkConfigBody, HostPortConfig, Ipv4Network,
PortConfigV1, RackNetworkConfigV1, RouteConfig as SledRouteConfig,
UplinkAddressConfig,
};
use std::{
collections::{hash_map::Entry, HashMap, HashSet},
Expand Down Expand Up @@ -922,7 +923,7 @@ impl BackgroundTask for SwitchPortSettingsManager {
};

let mut port_config = PortConfigV1 {
addresses: info.addresses.iter().map(|a| a.address).collect(),
addresses: info.addresses.iter().map(|a| UplinkAddressConfig {address: a.address, vlan_id: a.vlan_id.map(|v| v.into())}).collect(),
autoneg: info
.links
.get(0) //TODO breakout support
Expand Down Expand Up @@ -1400,7 +1401,14 @@ fn uplinks(
};
let config = HostPortConfig {
port: port.port_name.clone(),
addrs: config.addresses.iter().map(|a| a.address).collect(),
addrs: config
.addresses
.iter()
.map(|a| UplinkAddressConfig {
address: a.address,
vlan_id: a.vlan_id.map(|v| v.into()),
})
.collect(),
};

match uplinks.entry(*location) {
Expand Down
5 changes: 3 additions & 2 deletions nexus/src/app/rack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,8 @@ impl super::Nexus {
.iter()
.map(|a| Address {
address_lot: NameOrId::Name(address_lot_name.clone()),
address: (*a).into(),
address: a.address.into(),
vlan_id: a.vlan_id,
})
.collect();

Expand All @@ -537,7 +538,7 @@ impl super::Nexus {
.map(|r| Route {
dst: r.destination.into(),
gw: r.nexthop,
vid: None,
vid: r.vlan_id,
})
.collect();

Expand Down
1 change: 1 addition & 0 deletions nexus/tests/integration_tests/switch_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) {
AddressConfig {
addresses: vec![Address {
address: "203.0.113.10/24".parse().unwrap(),
vlan_id: None,
address_lot: NameOrId::Name("parkinglot".parse().unwrap()),
}],
},
Expand Down
3 changes: 3 additions & 0 deletions nexus/types/src/external_api/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,9 @@ pub struct Address {

/// The address and prefix length of this address.
pub address: IpNet,

/// Optional VLAN ID for this address
pub vlan_id: Option<u16>,
}

/// Select a port settings object by an optional name or id.
Expand Down
23 changes: 21 additions & 2 deletions openapi/bootstrap-agent.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,10 +765,10 @@
"type": "object",
"properties": {
"addresses": {
"description": "This port's addresses.",
"description": "This port's addresses and optional vlan IDs",
"type": "array",
"items": {
"$ref": "#/components/schemas/IpNetwork"
"$ref": "#/components/schemas/UplinkAddressConfig"
}
},
"autoneg": {
Expand Down Expand Up @@ -1250,6 +1250,25 @@
}
]
},
"UplinkAddressConfig": {
"type": "object",
"properties": {
"address": {
"$ref": "#/components/schemas/IpNetwork"
},
"vlan_id": {
"nullable": true,
"description": "The VLAN id associated with this route.",
"default": null,
"type": "integer",
"format": "uint16",
"minimum": 0
}
},
"required": [
"address"
]
},
"UserId": {
"description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.\n\n<details><summary>JSON schema</summary>\n\n```json { \"title\": \"A name unique within the parent collection\", \"description\": \"Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.\", \"type\": \"string\", \"maxLength\": 63, \"minLength\": 1, \"pattern\": \"^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z]([a-zA-Z0-9-]*[a-zA-Z0-9]+)?$\" } ``` </details>",
"type": "string"
Expand Down
23 changes: 21 additions & 2 deletions openapi/nexus-internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -3738,10 +3738,10 @@
"type": "object",
"properties": {
"addresses": {
"description": "This port's addresses.",
"description": "This port's addresses and optional vlan IDs",
"type": "array",
"items": {
"$ref": "#/components/schemas/IpNetwork"
"$ref": "#/components/schemas/UplinkAddressConfig"
}
},
"autoneg": {
Expand Down Expand Up @@ -4906,6 +4906,25 @@
"items"
]
},
"UplinkAddressConfig": {
"type": "object",
"properties": {
"address": {
"$ref": "#/components/schemas/IpNetwork"
},
"vlan_id": {
"nullable": true,
"description": "The VLAN id associated with this route.",
"default": null,
"type": "integer",
"format": "uint16",
"minimum": 0
}
},
"required": [
"address"
]
},
"UpstairsRepairType": {
"type": "string",
"enum": [
Expand Down
14 changes: 14 additions & 0 deletions openapi/nexus.json
Original file line number Diff line number Diff line change
Expand Up @@ -8996,6 +8996,13 @@
"$ref": "#/components/schemas/NameOrId"
}
]
},
"vlan_id": {
"nullable": true,
"description": "Optional VLAN ID for this address",
"type": "integer",
"format": "uint16",
"minimum": 0
}
},
"required": [
Expand Down Expand Up @@ -17268,6 +17275,13 @@
"description": "The port settings object this address configuration belongs to.",
"type": "string",
"format": "uuid"
},
"vlan_id": {
"nullable": true,
"description": "An optional VLAN ID",
"type": "integer",
"format": "uint16",
"minimum": 0
}
},
"required": [
Expand Down
Loading

0 comments on commit ac86b0d

Please sign in to comment.