Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions common/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ pub const AZ_PREFIX: u8 = 48;
pub const RACK_PREFIX: u8 = 56;
pub const SLED_PREFIX: u8 = 64;

// Multicast constants

/// IPv4 Source-Specific Multicast (SSM) subnet as defined in RFC 4607:
/// <https://tools.ietf.org/html/rfc4607>.
pub const IPV4_SSM_SUBNET: oxnet::Ipv4Net =
oxnet::Ipv4Net::new_unchecked(Ipv4Addr::new(232, 0, 0, 0), 8);

/// IPv6 Source-Specific Multicast (SSM) flag field value as defined in RFC 4607:
/// <https://tools.ietf.org/html/rfc4607>.
/// This is the flags nibble (high nibble of second byte) for FF3x::/32 addresses.
pub const IPV6_SSM_FLAG_FIELD: u8 = 3;

/// maximum possible value for a tcp or udp port
pub const MAX_PORT: u16 = u16::MAX;

Expand Down
22 changes: 21 additions & 1 deletion common/src/vlan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
//! VLAN ID wrapper.

use crate::api::external::Error;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::fmt;
use std::str::FromStr;

/// The maximum VLAN value (inclusive), as specified by IEEE 802.1Q.
pub const VLAN_MAX: u16 = 4094;

/// Wrapper around a VLAN ID, ensuring it is valid.
#[derive(Debug, Deserialize, Clone, Copy)]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Copy, JsonSchema)]
#[serde(rename = "VlanId")]
pub struct VlanID(u16);

impl VlanID {
Expand Down Expand Up @@ -44,3 +47,20 @@ impl FromStr for VlanID {
)
}
}

impl From<VlanID> for u16 {
fn from(vlan_id: VlanID) -> u16 {
vlan_id.0
}
}

impl slog::Value for VlanID {
fn serialize(
&self,
_record: &slog::Record,
key: slog::Key,
serializer: &mut dyn slog::Serializer,
) -> slog::Result {
serializer.emit_u16(key, self.0)
}
}
7 changes: 5 additions & 2 deletions end-to-end-tests/src/bin/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use end_to_end_tests::helpers::{
use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition};
use oxide_client::types::{
ByteCount, DeviceAccessTokenRequest, DeviceAuthRequest, DeviceAuthVerify,
DiskCreate, DiskSource, IpPoolCreate, IpPoolLinkSilo, IpVersion, NameOrId,
SiloQuotasUpdate,
DiskCreate, DiskSource, IpPoolCreate, IpPoolLinkSilo, IpPoolType,
IpVersion, NameOrId, SiloQuotasUpdate,
};
use oxide_client::{
ClientConsoleAuthExt, ClientDisksExt, ClientProjectsExt,
Expand Down Expand Up @@ -53,6 +53,9 @@ async fn run_test() -> Result<()> {
name: pool_name.parse().unwrap(),
description: "Default IP pool".to_string(),
ip_version,
mvlan: None,
pool_type: IpPoolType::Unicast,
switch_port_uplinks: None,
})
.send()
.await?;
Expand Down
7 changes: 5 additions & 2 deletions end-to-end-tests/src/bin/commtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use oxide_client::{
ClientSystemHardwareExt, ClientSystemIpPoolsExt, ClientSystemStatusExt,
ClientVpcsExt,
types::{
IpPoolCreate, IpPoolLinkSilo, IpRange, IpVersion, Name, NameOrId,
PingStatus, ProbeCreate, ProbeInfo, ProjectCreate,
IpPoolCreate, IpPoolLinkSilo, IpPoolType, IpRange, IpVersion, Name,
NameOrId, PingStatus, ProbeCreate, ProbeInfo, ProjectCreate,
UsernamePasswordCredentials,
},
};
Expand Down Expand Up @@ -295,6 +295,9 @@ async fn rack_prepare(
name: pool_name.parse().unwrap(),
description: "Default IP pool".to_string(),
ip_version,
mvlan: None,
pool_type: IpPoolType::Unicast,
switch_port_uplinks: None,
})
.send()
.await?;
Expand Down
2 changes: 2 additions & 0 deletions nexus/db-model/src/generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use diesel::pg::Pg;
use diesel::serialize::{self, ToSql};
use diesel::sql_types;
use omicron_common::api::external;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;

Expand All @@ -23,6 +24,7 @@ use std::convert::TryFrom;
FromSqlRow,
Serialize,
Deserialize,
JsonSchema,
)]
#[diesel(sql_type = sql_types::BigInt)]
#[repr(transparent)]
Expand Down
119 changes: 109 additions & 10 deletions nexus/db-model/src/ip_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! Model types for IP Pools and the CIDR blocks therein.

use crate::Name;
use crate::SqlU16;
use crate::collection::DatastoreCollectionConfig;
use crate::impl_enum_type;
use chrono::DateTime;
Expand All @@ -17,10 +18,10 @@ use nexus_db_schema::schema::ip_pool_range;
use nexus_db_schema::schema::ip_pool_resource;
use nexus_types::external_api::params;
use nexus_types::external_api::shared;
use nexus_types::external_api::shared::IpRange;
use nexus_types::external_api::views;
use nexus_types::identity::Resource;
use omicron_common::api::external;
use omicron_common::vlan::VlanID;
use std::net::IpAddr;
use uuid::Uuid;

Expand Down Expand Up @@ -72,6 +73,24 @@ impl From<IpVersion> for shared::IpVersion {
}
}

impl From<shared::IpPoolType> for IpPoolType {
fn from(value: shared::IpPoolType) -> Self {
match value {
shared::IpPoolType::Unicast => Self::Unicast,
shared::IpPoolType::Multicast => Self::Multicast,
}
}
}

impl From<IpPoolType> for shared::IpPoolType {
fn from(value: IpPoolType) -> Self {
match value {
IpPoolType::Unicast => Self::Unicast,
IpPoolType::Multicast => Self::Multicast,
}
}
}

/// An IP Pool is a collection of IP addresses external to the rack.
///
/// IP pools can be external or internal. External IP pools can be associated
Expand All @@ -82,16 +101,23 @@ impl From<IpVersion> for shared::IpVersion {
pub struct IpPool {
#[diesel(embed)]
pub identity: IpPoolIdentity,

/// The IP version of the pool.
pub ip_version: IpVersion,

/// Pool type for unicast (default) vs multicast pools.
pub pool_type: IpPoolType,
/// Switch port uplinks for multicast pools (array of switch port UUIDs).
/// Only applies to multicast pools, None for unicast pools.
pub switch_port_uplinks: Option<Vec<Uuid>>,
/// MVLAN ID for multicast pools.
/// Only applies to multicast pools, None for unicast pools.
pub mvlan: Option<SqlU16>,
/// Child resource generation number, for optimistic concurrency control of
/// the contained ranges.
pub rcgen: i64,
}

impl IpPool {
/// Creates a new unicast (default) IP pool.
pub fn new(
pool_identity: &external::IdentityMetadataCreateParams,
ip_version: IpVersion,
Expand All @@ -102,6 +128,29 @@ impl IpPool {
pool_identity.clone(),
),
ip_version,
pool_type: IpPoolType::Unicast,
switch_port_uplinks: None,
mvlan: None,
rcgen: 0,
}
}

/// Creates a new multicast IP pool.
pub fn new_multicast(
pool_identity: &external::IdentityMetadataCreateParams,
ip_version: IpVersion,
switch_port_uplinks: Option<Vec<Uuid>>,
mvlan: Option<VlanID>,
) -> Self {
Self {
identity: IpPoolIdentity::new(
Uuid::new_v4(),
pool_identity.clone(),
),
ip_version,
pool_type: IpPoolType::Multicast,
switch_port_uplinks,
mvlan: mvlan.map(|vid| u16::from(vid).into()),
rcgen: 0,
}
}
Expand All @@ -121,24 +170,55 @@ impl IpPool {

impl From<IpPool> for views::IpPool {
fn from(pool: IpPool) -> Self {
Self { identity: pool.identity(), ip_version: pool.ip_version.into() }
let identity = pool.identity();
let pool_type = pool.pool_type;

// Note: UUIDs expected to be converted to "switch.port" format in app
// layer, upon retrieval.
let switch_port_uplinks = match pool.switch_port_uplinks {
Some(uuid_list) => Some(
uuid_list.into_iter().map(|uuid| uuid.to_string()).collect(),
),
None => None,
};

let mvlan = pool.mvlan.map(|vlan| vlan.into());

Self {
identity,
pool_type: pool_type.into(),
ip_version: pool.ip_version.into(),
switch_port_uplinks,
mvlan,
}
}
}

/// A set of updates to an IP Pool
/// A set of updates to an IP Pool.
///
/// We do not modify the pool type after creation (e.g. unicast -> multicast or
/// vice versa), as that would require a migration of all associated resources.
#[derive(AsChangeset)]
#[diesel(table_name = ip_pool)]
pub struct IpPoolUpdate {
pub name: Option<Name>,
pub description: Option<String>,
/// Switch port uplinks for multicast pools (array of switch port UUIDs),
/// used for multicast traffic outbound from the rack to external networks.
pub switch_port_uplinks: Option<Vec<Uuid>>,
/// MVLAN ID for multicast pools.
pub mvlan: Option<SqlU16>,
pub time_modified: DateTime<Utc>,
}

// Used for unicast updates.
impl From<params::IpPoolUpdate> for IpPoolUpdate {
fn from(params: params::IpPoolUpdate) -> Self {
Self {
name: params.identity.name.map(|n| n.into()),
description: params.identity.description,
switch_port_uplinks: None, // no change
mvlan: None, // no change
time_modified: Utc::now(),
}
}
Expand All @@ -153,6 +233,25 @@ impl_enum_type!(
Silo => b"silo"
);

impl_enum_type!(
IpPoolTypeEnum:

#[derive(Clone, Copy, Debug, AsExpression, FromSqlRow, PartialEq)]
pub enum IpPoolType;

Unicast => b"unicast"
Multicast => b"multicast"
);

impl std::fmt::Display for IpPoolType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IpPoolType::Unicast => write!(f, "unicast"),
IpPoolType::Multicast => write!(f, "multicast"),
}
}
}

#[derive(Queryable, Insertable, Selectable, Clone, Copy, Debug, PartialEq)]
#[diesel(table_name = ip_pool_resource)]
pub struct IpPoolResource {
Expand Down Expand Up @@ -192,7 +291,7 @@ pub struct IpPoolRange {
}

impl IpPoolRange {
pub fn new(range: &IpRange, ip_pool_id: Uuid) -> Self {
pub fn new(range: &shared::IpRange, ip_pool_id: Uuid) -> Self {
let now = Utc::now();
let first_address = range.first_address();
let last_address = range.last_address();
Expand Down Expand Up @@ -221,20 +320,20 @@ impl From<IpPoolRange> for views::IpPoolRange {
id: range.id,
ip_pool_id: range.ip_pool_id,
time_created: range.time_created,
range: IpRange::from(&range),
range: shared::IpRange::from(&range),
}
}
}

impl From<&IpPoolRange> for IpRange {
impl From<&IpPoolRange> for shared::IpRange {
fn from(range: &IpPoolRange) -> Self {
let maybe_range =
match (range.first_address.ip(), range.last_address.ip()) {
(IpAddr::V4(first), IpAddr::V4(last)) => {
IpRange::try_from((first, last))
shared::IpRange::try_from((first, last))
}
(IpAddr::V6(first), IpAddr::V6(last)) => {
IpRange::try_from((first, last))
shared::IpRange::try_from((first, last))
}
(first, last) => {
unreachable!(
Expand Down
3 changes: 2 additions & 1 deletion nexus/db-model/src/schema_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
///
/// This must be updated when you change the database schema. Refer to
/// schema/crdb/README.adoc in the root of this repository for details.
pub const SCHEMA_VERSION: Version = Version::new(195, 0, 0);
pub const SCHEMA_VERSION: Version = Version::new(196, 0, 0);

/// List of all past database schema versions, in *reverse* order
///
Expand All @@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
// | leaving the first copy as an example for the next person.
// v
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
KnownVersion::new(196, "multicast-pool-support"),
KnownVersion::new(195, "tuf-pruned-index"),
KnownVersion::new(194, "tuf-pruned"),
KnownVersion::new(193, "nexus-lockstep-port"),
Expand Down
Loading
Loading