Skip to content

Commit 2a1d854

Browse files
authored
Add database support for dual-stack network interfaces (#9252)
- Add `ipv6` column to the `network_interface` table, and make both it and the `ip` column nullable to account for interfaces with one or both address families. - Add schema update files that recreate a bunch of views / indexes to account for the new columns. - Add a check constraint that there is at least one IP address, and update the code handling IP address exhaustion with that new constraint failure. - Closes #9242
1 parent 96764ff commit 2a1d854

File tree

31 files changed

+590
-163
lines changed

31 files changed

+590
-163
lines changed

dev-tools/omdb/src/bin/omdb/db.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ use indicatif::ProgressBar;
5959
use indicatif::ProgressDrawTarget;
6060
use indicatif::ProgressStyle;
6161
use internal_dns_types::names::ServiceName;
62-
use ipnetwork::IpNetwork;
6362
use nexus_config::PostgresConfigWithUrl;
6463
use nexus_config::RegionAllocationStrategy;
6564
use nexus_db_errors::OptionalError;
@@ -168,6 +167,8 @@ use std::collections::HashMap;
168167
use std::collections::HashSet;
169168
use std::fmt::Display;
170169
use std::future::Future;
170+
use std::net::Ipv4Addr;
171+
use std::net::Ipv6Addr;
171172
use std::num::NonZeroU32;
172173
use std::str::FromStr;
173174
use std::sync::Arc;
@@ -5376,12 +5377,16 @@ async fn cmd_db_network_list_vnics(
53765377
#[derive(Tabled)]
53775378
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
53785379
struct NicRow {
5379-
ip: IpNetwork,
5380+
#[tabled(display_with = "option_impl_display")]
5381+
ipv4: Option<Ipv4Addr>,
5382+
#[tabled(display_with = "option_impl_display")]
5383+
ipv6: Option<Ipv6Addr>,
53805384
mac: MacAddr,
53815385
slot: u8,
53825386
primary: bool,
53835387
kind: &'static str,
5384-
subnet: String,
5388+
ipv4_subnet: String,
5389+
ipv6_subnet: String,
53855390
parent_id: Uuid,
53865391
parent_name: String,
53875392
}
@@ -5471,7 +5476,7 @@ async fn cmd_db_network_list_vnics(
54715476
}
54725477
};
54735478

5474-
let subnet = {
5479+
let (ipv4_subnet, ipv6_subnet) = {
54755480
use nexus_db_schema::schema::vpc_subnet::dsl;
54765481
let subnet = match dsl::vpc_subnet
54775482
.filter(dsl::id.eq(nic.subnet_id))
@@ -5488,28 +5493,36 @@ async fn cmd_db_network_list_vnics(
54885493
continue;
54895494
}
54905495
};
5491-
5492-
if nic.ip.is_ipv4() {
5496+
let ipv4_subnet = if nic.ipv4.is_some() {
54935497
subnet.ipv4_block.to_string()
54945498
} else {
5499+
String::from("-")
5500+
};
5501+
let ipv6_subnet = if nic.ipv6.is_some() {
54955502
subnet.ipv6_block.to_string()
5496-
}
5503+
} else {
5504+
String::from("-")
5505+
};
5506+
(ipv4_subnet, ipv6_subnet)
54975507
};
54985508

54995509
let row = NicRow {
5500-
ip: nic.ip,
5510+
ipv4: nic.ipv4.map(Into::into),
5511+
ipv6: nic.ipv6.map(Into::into),
55015512
mac: *nic.mac,
55025513
slot: *nic.slot,
55035514
primary: nic.primary,
55045515
kind,
5505-
subnet,
5516+
ipv4_subnet,
5517+
ipv6_subnet,
55065518
parent_id: nic.parent_id,
55075519
parent_name,
55085520
};
55095521
rows.push(row);
55105522
}
55115523

5512-
rows.sort_by(|a, b| a.ip.cmp(&b.ip));
5524+
// Sort by IPv4 address, and then IPv6 address.
5525+
rows.sort_by(|a, b| a.ipv4.cmp(&b.ipv4).then_with(|| a.ipv6.cmp(&b.ipv6)));
55135526
let table = tabled::Table::new(rows)
55145527
.with(tabled::settings::Style::empty())
55155528
.to_string();

nexus/db-model/src/ipv4.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Database-friendly IPv4 addresses
6+
7+
use diesel::backend::Backend;
8+
use diesel::deserialize;
9+
use diesel::deserialize::FromSql;
10+
use diesel::pg::Pg;
11+
use diesel::serialize;
12+
use diesel::serialize::Output;
13+
use diesel::serialize::ToSql;
14+
use diesel::sql_types::Inet;
15+
use ipnetwork::IpNetwork;
16+
use ipnetwork::Ipv4Network;
17+
use omicron_common::api::external::Error;
18+
use serde::Deserialize;
19+
use serde::Serialize;
20+
21+
#[derive(
22+
Clone,
23+
Copy,
24+
AsExpression,
25+
FromSqlRow,
26+
PartialEq,
27+
Ord,
28+
PartialOrd,
29+
Eq,
30+
Deserialize,
31+
Serialize,
32+
)]
33+
#[diesel(sql_type = Inet)]
34+
pub struct Ipv4Addr(std::net::Ipv4Addr);
35+
36+
NewtypeDebug! { () pub struct Ipv4Addr(std::net::Ipv4Addr); }
37+
NewtypeFrom! { () pub struct Ipv4Addr(std::net::Ipv4Addr); }
38+
NewtypeDeref! { () pub struct Ipv4Addr(std::net::Ipv4Addr); }
39+
40+
impl From<&std::net::Ipv4Addr> for Ipv4Addr {
41+
fn from(addr: &std::net::Ipv4Addr) -> Self {
42+
Self(*addr)
43+
}
44+
}
45+
46+
impl From<Ipv4Addr> for std::net::IpAddr {
47+
fn from(value: Ipv4Addr) -> Self {
48+
std::net::IpAddr::from(value.0)
49+
}
50+
}
51+
52+
impl From<&Ipv4Addr> for std::net::IpAddr {
53+
fn from(value: &Ipv4Addr) -> Self {
54+
(*value).into()
55+
}
56+
}
57+
58+
impl From<Ipv4Addr> for Ipv4Network {
59+
fn from(value: Ipv4Addr) -> Self {
60+
Ipv4Network::from(value.0)
61+
}
62+
}
63+
64+
impl From<Ipv4Addr> for IpNetwork {
65+
fn from(value: Ipv4Addr) -> Self {
66+
IpNetwork::V4(Ipv4Network::from(value.0))
67+
}
68+
}
69+
70+
impl ToSql<Inet, Pg> for Ipv4Addr {
71+
fn to_sql<'a>(&'a self, out: &mut Output<'a, '_, Pg>) -> serialize::Result {
72+
let net = IpNetwork::V4(Ipv4Network::from(self.0));
73+
<IpNetwork as ToSql<Inet, Pg>>::to_sql(&net, &mut out.reborrow())
74+
}
75+
}
76+
77+
impl<DB> FromSql<Inet, DB> for Ipv4Addr
78+
where
79+
DB: Backend,
80+
IpNetwork: FromSql<Inet, DB>,
81+
{
82+
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
83+
match IpNetwork::from_sql(bytes)?.ip() {
84+
std::net::IpAddr::V4(ip) => Ok(Self(ip)),
85+
v6 => {
86+
Err(Box::new(Error::internal_error(
87+
format!("Expected an IPv4 address from the database, found IPv6: '{}'", v6).as_str()
88+
)))
89+
}
90+
}
91+
}
92+
}

nexus/db-model/src/ipv6.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,30 @@ impl From<&std::net::Ipv6Addr> for Ipv6Addr {
4242
}
4343
}
4444

45+
impl From<Ipv6Addr> for std::net::IpAddr {
46+
fn from(value: Ipv6Addr) -> Self {
47+
value.0.into()
48+
}
49+
}
50+
51+
impl From<&Ipv6Addr> for std::net::IpAddr {
52+
fn from(value: &Ipv6Addr) -> Self {
53+
(*value).into()
54+
}
55+
}
56+
57+
impl From<Ipv6Addr> for Ipv6Network {
58+
fn from(value: Ipv6Addr) -> Self {
59+
Ipv6Network::from(value.0)
60+
}
61+
}
62+
63+
impl From<Ipv6Addr> for IpNetwork {
64+
fn from(value: Ipv6Addr) -> Self {
65+
IpNetwork::V6(Ipv6Network::from(value.0))
66+
}
67+
}
68+
4569
impl ToSql<Inet, Pg> for Ipv6Addr {
4670
fn to_sql<'a>(&'a self, out: &mut Output<'a, '_, Pg>) -> serialize::Result {
4771
let net = IpNetwork::V6(Ipv6Network::from(self.0));

nexus/db-model/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ mod internet_gateway;
5151
mod inventory;
5252
mod ip_pool;
5353
mod ipnet;
54+
pub mod ipv4;
5455
mod ipv4net;
5556
pub mod ipv6;
5657
mod ipv6net;
@@ -192,6 +193,7 @@ pub use internet_gateway::*;
192193
pub use inventory::*;
193194
pub use ip_pool::*;
194195
pub use ipnet::*;
196+
pub use ipv4::*;
195197
pub use ipv4net::*;
196198
pub use ipv6::*;
197199
pub use ipv6net::*;

0 commit comments

Comments
 (0)