Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live attach/detach of external IPs #4694

Merged
merged 63 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
eddd29c
Brutally hacked together, saga-less live FIP mgmt
FelixMcFelix Nov 29, 2023
34c03d7
The sagaization begins
FelixMcFelix Dec 13, 2023
c6235e3
Theoretically in a testable state post-sagaization
FelixMcFelix Dec 14, 2023
89acdba
Working sagas
FelixMcFelix Dec 15, 2023
5366162
`cargo fix`
FelixMcFelix Dec 15, 2023
b2d08f7
Back out iff. migration in progress.
FelixMcFelix Dec 15, 2023
7d6118f
Merge branch 'main' into felixmcfelix/floating-ip-live
FelixMcFelix Dec 15, 2023
81c7d25
Make use of `check_and_update` for attach/detach
FelixMcFelix Dec 18, 2023
a0fe2fd
Fixes to atomic attach/detach/delete
FelixMcFelix Dec 19, 2023
58f1760
Merge branch 'main' into felixmcfelix/floating-ip-live
FelixMcFelix Dec 19, 2023
5bc4789
Better revert of external IPs on sled-agent err
FelixMcFelix Dec 19, 2023
7034efd
Add attach state to external IPs.
FelixMcFelix Dec 20, 2023
ce1c92d
Bad state in detach
FelixMcFelix Dec 20, 2023
cc130c3
Make use of `Instance::attach_resource` and friends
FelixMcFelix Dec 21, 2023
51f8bae
Block instance_start while attaching/detaching
FelixMcFelix Dec 21, 2023
a1b558e
Refactor, resolve interaction with instance delete saga.
FelixMcFelix Dec 22, 2023
c0ffb93
Add EIP state to omdb, clean up error msg on double attach/detach
FelixMcFelix Dec 22, 2023
ee6a790
The great clippy appeasement
FelixMcFelix Dec 22, 2023
0001eb3
WIP test fixes, resume the quest for idempotency
FelixMcFelix Dec 22, 2023
89ddffa
Large block comment for myself/future historians
FelixMcFelix Dec 22, 2023
9db79b9
Test harness progress
FelixMcFelix Dec 27, 2023
c259342
Clippy + neuter errors in undo path
FelixMcFelix Dec 28, 2023
21d5776
Working idempotent double attach/detach.
FelixMcFelix Dec 28, 2023
dcdec48
Revalidate consumers of `instance_lookup_external_ips`
FelixMcFelix Dec 28, 2023
4b65a67
(Existing) Test fixup.
FelixMcFelix Dec 28, 2023
ec3e01e
Accidentally forgot an 'IF NOT EXISTS'
FelixMcFelix Dec 28, 2023
7800b07
Fill out unauthorized endpoint tests.
FelixMcFelix Dec 28, 2023
2e6f972
Merge branch 'main' into felixmcfelix/floating-ip-live
FelixMcFelix Dec 29, 2023
24591b3
Additional integration tests.
FelixMcFelix Dec 29, 2023
019708f
Tests: one more final
FelixMcFelix Dec 29, 2023
e5f549a
Partial self-review, skipping the tricky bits.
FelixMcFelix Dec 29, 2023
14bb57f
Allow idempotent ephemeral IP attach if pool empty
FelixMcFelix Jan 3, 2024
04b66f7
Merge branch 'main' into felixmcfelix/floating-ip-live
FelixMcFelix Jan 3, 2024
0392931
Minor comment typo.
FelixMcFelix Jan 3, 2024
239fb70
Minor fn breakout before larger refactor
FelixMcFelix Jan 3, 2024
54c969a
Factor out Floating+Ephemeral attach logic
FelixMcFelix Jan 3, 2024
9200b4a
Factor out detach method bodies.
FelixMcFelix Jan 3, 2024
877de91
Minor cleanup of main datastore changes.
FelixMcFelix Jan 3, 2024
945db75
final(?) cleanup.
FelixMcFelix Jan 3, 2024
169d200
Merge branch 'main' into felixmcfelix/floating-ip-live
FelixMcFelix Jan 5, 2024
ed48f03
Review feedback: `NameOrId` for all create/attach/detach
FelixMcFelix Jan 5, 2024
9680c95
Self review: missed comment
FelixMcFelix Jan 5, 2024
59f665c
Review feedback: Explicit fail on double detach Ephemeral
FelixMcFelix Jan 5, 2024
1c0be9d
Review feedback: enhance `views::ExternalIp`
FelixMcFelix Jan 5, 2024
f26aa64
Fix up end-to-end tests with new IP structure.
FelixMcFelix Jan 5, 2024
6184ecc
Bump up schema version pre-merge
FelixMcFelix Jan 5, 2024
b9d783c
Merge branch 'main' into felixmcfelix/floating-ip-live
FelixMcFelix Jan 5, 2024
73db560
Banish `NameOrId` resolution from datastore/external-ip
FelixMcFelix Jan 5, 2024
5a916ca
Review feedback: use `Nexus::ip_pool_lookup`
FelixMcFelix Jan 6, 2024
725efa4
Review feedback: nits and error messages
FelixMcFelix Jan 10, 2024
f5a50b0
Review feedback: correct lockout check with live migration
FelixMcFelix Jan 10, 2024
d1519b1
Self review: missed some comments
FelixMcFelix Jan 10, 2024
705cb6e
Self review: comment expansion
FelixMcFelix Jan 10, 2024
774c183
Whitespace...
FelixMcFelix Jan 10, 2024
f1cd2b1
Unduplicate calls to `ensure_nat_entry`
FelixMcFelix Jan 10, 2024
540de27
Review feedback: tougher NAT cleanup when undoing attach
FelixMcFelix Jan 11, 2024
a7bf681
Review feedback: unwind on concurrent delete
FelixMcFelix Jan 11, 2024
b288320
Review feedback: begin work on separate FIP + EIP endpoints
FelixMcFelix Jan 19, 2024
6c2bdf0
Move schema in prep for merge
FelixMcFelix Jan 19, 2024
baa62f7
Merge branch 'main' into felixmcfelix/floating-ip-live
FelixMcFelix Jan 19, 2024
42afeda
Add separate ephemeral IP manipulation endpoint
FelixMcFelix Jan 19, 2024
5a4614c
Excise instance/external-ip/attach + detach
FelixMcFelix Jan 19, 2024
ee146c2
Remove autogen'd file
FelixMcFelix Jan 19, 2024
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
3 changes: 3 additions & 0 deletions dev-tools/omdb/src/bin/omdb/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use nexus_db_model::ExternalIp;
use nexus_db_model::HwBaseboardId;
use nexus_db_model::Instance;
use nexus_db_model::InvCollection;
use nexus_db_model::IpAttachState;
use nexus_db_model::Project;
use nexus_db_model::Region;
use nexus_db_model::RegionSnapshot;
Expand Down Expand Up @@ -1653,6 +1654,7 @@ async fn cmd_db_eips(
ip: ipnetwork::IpNetwork,
ports: PortRange,
kind: String,
state: IpAttachState,
owner: Owner,
}

Expand Down Expand Up @@ -1737,6 +1739,7 @@ async fn cmd_db_eips(
first: ip.first_port.into(),
last: ip.last_port.into(),
},
state: ip.state,
kind: format!("{:?}", ip.kind),
owner,
};
Expand Down
13 changes: 8 additions & 5 deletions end-to-end-tests/src/instance_launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use anyhow::{ensure, Context as _, Result};
use async_trait::async_trait;
use omicron_test_utils::dev::poll::{wait_for_condition, CondCheckError};
use oxide_client::types::{
ByteCount, DiskCreate, DiskSource, ExternalIpCreate, InstanceCpuCount,
InstanceCreate, InstanceDiskAttachment, InstanceNetworkInterfaceAttachment,
SshKeyCreate,
ByteCount, DiskCreate, DiskSource, ExternalIp, ExternalIpCreate,
InstanceCpuCount, InstanceCreate, InstanceDiskAttachment,
InstanceNetworkInterfaceAttachment, SshKeyCreate,
};
use oxide_client::{ClientDisksExt, ClientInstancesExt, ClientSessionExt};
use russh::{ChannelMsg, Disconnect};
Expand Down Expand Up @@ -70,7 +70,7 @@ async fn instance_launch() -> Result<()> {
name: disk_name.clone(),
}],
network_interfaces: InstanceNetworkInterfaceAttachment::Default,
external_ips: vec![ExternalIpCreate::Ephemeral { pool_name: None }],
external_ips: vec![ExternalIpCreate::Ephemeral { pool: None }],
user_data: String::new(),
start: true,
})
Expand All @@ -87,7 +87,10 @@ async fn instance_launch() -> Result<()> {
.items
.first()
.context("no external IPs")?
.ip;
.clone();
let ExternalIp::Ephemeral { ip: ip_addr } = ip_addr else {
anyhow::bail!("IP bound to instance was not ephemeral as required.")
};
eprintln!("instance external IP: {}", ip_addr);

// poll serial for login prompt, waiting 5 min max
Expand Down
10 changes: 10 additions & 0 deletions illumos-utils/src/opte/illumos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use omicron_common::api::internal::shared::NetworkInterfaceKind;
use opte_ioctl::OpteHdl;
use slog::info;
use slog::Logger;
use std::net::IpAddr;

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand Down Expand Up @@ -46,6 +47,15 @@ pub enum Error {

#[error("Tried to release non-existent port ({0}, {1:?})")]
ReleaseMissingPort(uuid::Uuid, NetworkInterfaceKind),

#[error("Tried to update external IPs on non-existent port ({0}, {1:?})")]
ExternalIpUpdateMissingPort(uuid::Uuid, NetworkInterfaceKind),

#[error("Could not find Primary NIC")]
NoPrimaryNic,

#[error("Can't attach new ephemeral IP {0}, currently have {1}")]
ImplicitEphemeralIpDetach(IpAddr, IpAddr),
}

/// Delete all xde devices on the system.
Expand Down
10 changes: 10 additions & 0 deletions illumos-utils/src/opte/non_illumos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use slog::Logger;

use crate::addrobj::AddrObject;
use omicron_common::api::internal::shared::NetworkInterfaceKind;
use std::net::IpAddr;

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand All @@ -16,6 +17,15 @@ pub enum Error {

#[error("Tried to release non-existent port ({0}, {1:?})")]
ReleaseMissingPort(uuid::Uuid, NetworkInterfaceKind),

#[error("Tried to update external IPs on non-existent port ({0}, {1:?})")]
ExternalIpUpdateMissingPort(uuid::Uuid, NetworkInterfaceKind),

#[error("Could not find Primary NIC")]
NoPrimaryNic,

#[error("Can't attach new ephemeral IP {0}, currently have {1}")]
ImplicitEphemeralIpDetach(IpAddr, IpAddr),
}

pub fn initialize_xde_driver(
Expand Down
116 changes: 116 additions & 0 deletions illumos-utils/src/opte/port_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use oxide_vpc::api::MacAddr;
use oxide_vpc::api::RouterTarget;
use oxide_vpc::api::SNat4Cfg;
use oxide_vpc::api::SNat6Cfg;
use oxide_vpc::api::SetExternalIpsReq;
use oxide_vpc::api::VpcCfg;
use slog::debug;
use slog::error;
Expand Down Expand Up @@ -401,6 +402,121 @@ impl PortManager {
Ok((port, ticket))
}

/// Ensure external IPs for an OPTE port are up to date.
#[cfg_attr(not(target_os = "illumos"), allow(unused_variables))]
pub fn external_ips_ensure(
&self,
nic_id: Uuid,
nic_kind: NetworkInterfaceKind,
source_nat: Option<SourceNatConfig>,
ephemeral_ip: Option<IpAddr>,
floating_ips: &[IpAddr],
) -> Result<(), Error> {
let ports = self.inner.ports.lock().unwrap();
let port = ports.get(&(nic_id, nic_kind)).ok_or_else(|| {
Error::ExternalIpUpdateMissingPort(nic_id, nic_kind)
})?;

// XXX: duplicates parts of macro logic in `create_port`.
macro_rules! ext_ip_cfg {
($ip:expr, $log_prefix:literal, $ip_t:path, $cidr_t:path,
$ipcfg_e:path, $ipcfg_t:ident, $snat_t:ident) => {{
let snat = match source_nat {
Some(snat) => {
let $ip_t(snat_ip) = snat.ip else {
error!(
self.inner.log,
concat!($log_prefix, " SNAT config");
"snat_ip" => ?snat.ip,
);
return Err(Error::InvalidPortIpConfig);
};
let ports = snat.first_port..=snat.last_port;
Some($snat_t { external_ip: snat_ip.into(), ports })
}
None => None,
};
let ephemeral_ip = match ephemeral_ip {
Some($ip_t(ip)) => Some(ip.into()),
Some(_) => {
error!(
self.inner.log,
concat!($log_prefix, " ephemeral IP");
"ephemeral_ip" => ?ephemeral_ip,
);
return Err(Error::InvalidPortIpConfig);
}
None => None,
};
let floating_ips: Vec<_> = floating_ips
.iter()
.copied()
.map(|ip| match ip {
$ip_t(ip) => Ok(ip.into()),
_ => {
error!(
self.inner.log,
concat!($log_prefix, " ephemeral IP");
"ephemeral_ip" => ?ephemeral_ip,
);
Err(Error::InvalidPortIpConfig)
}
})
.collect::<Result<Vec<_>, _>>()?;

ExternalIpCfg {
ephemeral_ip,
snat,
floating_ips,
}
}}
}

// TODO-completeness: support dual-stack. We'll need to explicitly store
// a v4 and a v6 ephemeral IP + SNat + gateway + ... in `InstanceInner`
// to have enough info to build both.
let mut v4_cfg = None;
let mut v6_cfg = None;
match port.gateway().ip {
IpAddr::V4(_) => {
v4_cfg = Some(ext_ip_cfg!(
ip,
"Expected IPv4",
IpAddr::V4,
IpCidr::Ip4,
IpCfg::Ipv4,
Ipv4Cfg,
SNat4Cfg
))
}
IpAddr::V6(_) => {
v6_cfg = Some(ext_ip_cfg!(
ip,
"Expected IPv6",
IpAddr::V6,
IpCidr::Ip6,
IpCfg::Ipv6,
Ipv6Cfg,
SNat6Cfg
))
}
}

let req = SetExternalIpsReq {
port_name: port.name().into(),
external_ips_v4: v4_cfg,
external_ips_v6: v6_cfg,
};

#[cfg(target_os = "illumos")]
let hdl = opte_ioctl::OpteHdl::open(opte_ioctl::OpteHdl::XDE_CTL)?;

#[cfg(target_os = "illumos")]
hdl.set_external_ips(&req)?;

Ok(())
}

#[cfg(target_os = "illumos")]
pub fn firewall_rules_ensure(
&self,
Expand Down
Loading
Loading