Skip to content

Commit ce3102b

Browse files
authored
Fixes improves OPTE installation and improves errors (#1052)
* Fixes improves OPTE installation and improves errors - Fixes mismerge in `tools/install_opte.sh` that prevented installing OPTE package on a system that didn't previously have it - Improves error messages when either xde driver fails or the expected virtual networking devices don't exist * Fmt, fix lifetime for automock * Add better handling and cleanup of VNICs - Adds a `VnicKind` enum for tracking the flavor of each VNIC the sled agent is responsible for. - Adds parameter to the `Dladm::get_vnics()` call which filters the returned list to a particular kind. The goal here is to be more explicit about which VNICs we're looking for and ultimately operating on in the sled agent. - The sled agent now cleans up guest VNICs and the underlying xde devices (OPTE ports) when it starts up, similar to how it clears out any extant control VNICs, to ensure things are in a reliable state before accepting any requests from Nexus - Improves the `tools/install_opte.sh` script, trying to be less intrusive and only modifying the state we need to change when adding the OPTE / xde package repositories. * Add mock_opte::delete_all_xde_devices for non-illumos systems * Update sled-agent mock context calls * Be more conservative about what VnicKinds are allowed * non-sticky is the goal * Remove kind parameter to get_vnics, no longer needed
1 parent c44eb0e commit ce3102b

File tree

9 files changed

+199
-35
lines changed

9 files changed

+199
-35
lines changed

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sled-agent/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ vsss-rs = { version = "2.0.0-pre2", default-features = false, features = ["std"]
4848
zone = "0.1"
4949

5050
[target.'cfg(target_os = "illumos")'.dependencies]
51-
opte-ioctl = { git = "https://github.com/oxidecomputer/opte", rev = "cb1767c" }
52-
opte = { git = "https://github.com/oxidecomputer/opte", rev = "cb1767c", features = [ "api", "std" ] }
51+
opte-ioctl = { git = "https://github.com/oxidecomputer/opte", rev = "b998015" }
52+
opte = { git = "https://github.com/oxidecomputer/opte", rev = "b998015", features = [ "api", "std" ] }
5353

5454
[dev-dependencies]
5555
expectorate = "1.0.5"

sled-agent/src/illumos/dladm.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! Utilities for poking at data links.
66
77
use crate::common::vlan::VlanID;
8+
use crate::illumos::vnic::VnicKind;
89
use crate::illumos::{execute, ExecutionError, PFEXEC};
910
use omicron_common::api::external::MacAddr;
1011
use serde::{Deserialize, Serialize};
@@ -13,6 +14,11 @@ use std::str::FromStr;
1314
pub const VNIC_PREFIX: &str = "ox";
1415
pub const VNIC_PREFIX_CONTROL: &str = "oxControl";
1516

17+
/// Prefix used to name VNICs over xde devices / OPTE ports.
18+
// TODO-correctness: Remove this when `xde` devices can be directly used beneath
19+
// Viona, and thus plumbed directly to guests.
20+
pub const VNIC_PREFIX_GUEST: &str = "vopte";
21+
1622
pub const DLADM: &str = "/usr/sbin/dladm";
1723

1824
/// Errors returned from [`Dladm::find_physical`].
@@ -164,16 +170,22 @@ impl Dladm {
164170
Ok(())
165171
}
166172

167-
/// Returns all VNICs that may be managed by the Sled Agent.
173+
/// Returns VNICs that may be managed by the Sled Agent.
168174
pub fn get_vnics() -> Result<Vec<String>, GetVnicError> {
169175
let mut command = std::process::Command::new(PFEXEC);
170176
let cmd = command.args(&[DLADM, "show-vnic", "-p", "-o", "LINK"]);
171177
let output = execute(cmd).map_err(|err| GetVnicError { err })?;
172178

173179
let vnics = String::from_utf8_lossy(&output.stdout)
174180
.lines()
175-
.filter(|vnic| vnic.starts_with(VNIC_PREFIX))
176-
.map(|s| s.to_owned())
181+
.filter_map(|name| {
182+
// Ensure this is a kind of VNIC that the sled agent could be
183+
// responsible for.
184+
match VnicKind::from_name(name) {
185+
Some(_) => Some(name.to_owned()),
186+
None => None,
187+
}
188+
})
177189
.collect();
178190
Ok(vnics)
179191
}

sled-agent/src/illumos/running_zone.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,14 @@ impl RunningZone {
221221
},
222222
)?;
223223

224+
let control_vnic = Vnic::wrap_existing(vnic_name)
225+
.expect("Failed to wrap valid control VNIC");
226+
224227
Ok(Self {
225228
inner: InstalledZone {
226229
log: log.new(o!("zone" => zone_name.to_string())),
227230
name: zone_name.to_string(),
228-
control_vnic: Vnic::wrap_existing(vnic_name),
231+
control_vnic,
229232
// TODO(https://github.com/oxidecomputer/omicron/issues/725)
230233
//
231234
// Re-initialize guest_vnic state by inspecting the zone.

sled-agent/src/illumos/vnic.rs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
use crate::illumos::dladm::{
88
CreateVnicError, DeleteVnicError, PhysicalLink, VNIC_PREFIX,
9-
VNIC_PREFIX_CONTROL,
9+
VNIC_PREFIX_CONTROL, VNIC_PREFIX_GUEST,
1010
};
1111
use omicron_common::api::external::MacAddr;
1212
use std::sync::{
@@ -60,7 +60,7 @@ impl VnicAllocator {
6060
debug_assert!(name.starts_with(VNIC_PREFIX));
6161
debug_assert!(name.starts_with(VNIC_PREFIX_CONTROL));
6262
Dladm::create_vnic(&self.data_link, &name, mac, None)?;
63-
Ok(Vnic { name, deleted: false })
63+
Ok(Vnic { name, deleted: false, kind: VnicKind::OxideControl })
6464
}
6565

6666
fn new_superscope<S: AsRef<str>>(&self, scope: S) -> Self {
@@ -82,6 +82,32 @@ impl VnicAllocator {
8282
}
8383
}
8484

85+
/// Represents the kind of a VNIC, such as whether it's for guest networking or
86+
/// communicating with Oxide services.
87+
#[derive(Debug, Clone, Copy, PartialEq)]
88+
pub enum VnicKind {
89+
OxideControl,
90+
Guest,
91+
}
92+
93+
impl VnicKind {
94+
/// Infer the kind from a VNIC's name, if this one the sled agent can
95+
/// manage, and `None` otherwise.
96+
pub fn from_name(name: &str) -> Option<Self> {
97+
if name.starts_with(VNIC_PREFIX) {
98+
Some(VnicKind::OxideControl)
99+
} else if name.starts_with(VNIC_PREFIX_GUEST) {
100+
Some(VnicKind::Guest)
101+
} else {
102+
None
103+
}
104+
}
105+
}
106+
107+
#[derive(thiserror::Error, Debug)]
108+
#[error("VNIC with name '{0}' is not valid for sled agent management")]
109+
pub struct InvalidVnicKind(String);
110+
85111
/// Represents an allocated VNIC on the system.
86112
/// The VNIC is de-allocated when it goes out of scope.
87113
///
@@ -92,12 +118,22 @@ impl VnicAllocator {
92118
pub struct Vnic {
93119
name: String,
94120
deleted: bool,
121+
kind: VnicKind,
95122
}
96123

97124
impl Vnic {
98125
/// Takes ownership of an existing VNIC.
99-
pub fn wrap_existing<S: AsRef<str>>(name: S) -> Self {
100-
Vnic { name: name.as_ref().to_owned(), deleted: false }
126+
pub fn wrap_existing<S: AsRef<str>>(
127+
name: S,
128+
) -> Result<Self, InvalidVnicKind> {
129+
match VnicKind::from_name(name.as_ref()) {
130+
Some(kind) => Ok(Vnic {
131+
name: name.as_ref().to_owned(),
132+
deleted: false,
133+
kind,
134+
}),
135+
None => Err(InvalidVnicKind(name.as_ref().to_owned())),
136+
}
101137
}
102138

103139
/// Deletes a NIC (if it has not already been deleted).
@@ -113,6 +149,10 @@ impl Vnic {
113149
pub fn name(&self) -> &str {
114150
&self.name
115151
}
152+
153+
pub fn kind(&self) -> VnicKind {
154+
self.kind
155+
}
116156
}
117157

118158
impl Drop for Vnic {

sled-agent/src/opte/mock_opte.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,8 @@ pub fn initialize_xde_driver(log: &Logger) -> Result<(), Error> {
183183
slog::warn!(log, "`xde` driver is a fiction on non-illumos systems");
184184
Ok(())
185185
}
186+
187+
pub fn delete_all_xde_devices(log: &Logger) -> Result<(), Error> {
188+
slog::warn!(log, "`xde` driver is a fiction on non-illumos systems");
189+
Ok(())
190+
}

sled-agent/src/opte/opte.rs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ use std::sync::atomic::AtomicU64;
2828
use std::sync::atomic::Ordering;
2929
use std::sync::Arc;
3030

31+
// Names of VNICs used as underlay devices for the xde driver.
32+
const XDE_VNIC_NAMES: [&str; 2] = ["net0", "net1"];
33+
34+
// Prefix used to identify xde data links.
35+
const XDE_LINK_PREFIX: &str = "opte";
36+
3137
#[derive(thiserror::Error, Debug)]
3238
pub enum Error {
3339
#[error("Failure interacting with the OPTE ioctl(2) interface: {0}")]
@@ -37,7 +43,15 @@ pub enum Error {
3743
CreateVnic(#[from] dladm::CreateVnicError),
3844

3945
#[error("Failed to create an IPv6 link-local address for xde underlay devices: {0}")]
40-
UnderlayDevice(#[from] crate::illumos::ExecutionError),
46+
UnderlayDeviceAddress(#[from] crate::illumos::ExecutionError),
47+
48+
#[error("Failed to get VNICs for xde underlay devices: {0}")]
49+
GetVnic(#[from] crate::illumos::dladm::GetVnicError),
50+
51+
#[error(
52+
"No xde driver configuration file exists at '/kernel/drv/xde.conf'"
53+
)]
54+
NoXdeConf,
4155

4256
#[error(transparent)]
4357
BadAddrObj(#[from] addrobj::ParseError),
@@ -54,7 +68,7 @@ impl OptePortAllocator {
5468
}
5569

5670
fn next(&self) -> String {
57-
format!("opte{}", self.next_id())
71+
format!("{}{}", XDE_LINK_PREFIX, self.next_id())
5872
}
5973

6074
fn next_id(&self) -> u64 {
@@ -150,7 +164,9 @@ impl OptePortAllocator {
150164
Some(omicron_common::api::external::MacAddr(mac)),
151165
None,
152166
)?;
153-
Some(Vnic::wrap_existing(vnic_name))
167+
// Safety: We're explicitly creating the VNIC with the prefix
168+
// `VNIC_PREFIX_GUEST`, so this call must return Some(_).
169+
Some(Vnic::wrap_existing(vnic_name).unwrap())
154170
};
155171

156172
Ok(OptePort {
@@ -258,16 +274,41 @@ impl Drop for OptePort {
258274
}
259275
}
260276

277+
/// Delete all xde devices on the system.
278+
pub fn delete_all_xde_devices(log: &Logger) -> Result<(), Error> {
279+
let hdl = OpteHdl::open(OpteHdl::DLD_CTL)?;
280+
for port_info in hdl.list_ports()?.ports.into_iter() {
281+
let name = &port_info.name;
282+
info!(
283+
log,
284+
"deleting existing OPTE port and xde device";
285+
"device_name" => name
286+
);
287+
hdl.delete_xde(name)?;
288+
}
289+
Ok(())
290+
}
291+
261292
/// Initialize the underlay devices required for the xde kernel module.
262293
///
263294
/// The xde driver needs information about the physical devices out which it can
264295
/// send traffic from the guests.
265296
pub fn initialize_xde_driver(log: &Logger) -> Result<(), Error> {
297+
if !std::path::Path::new("/kernel/drv/xde.conf").exists() {
298+
return Err(Error::NoXdeConf);
299+
}
266300
let underlay_nics = find_chelsio_links()?;
267301
info!(log, "using '{:?}' as data links for xde driver", underlay_nics);
268302
if underlay_nics.len() < 2 {
303+
const MESSAGE: &str = concat!(
304+
"There must be at least two underlay NICs for the xde ",
305+
"driver to operate. These are currently created by ",
306+
"`./tools/create_virtual_hardware.sh`. Please ensure that ",
307+
"script has been run, and that two VNICs named `net{0,1}` ",
308+
"exist on the system."
309+
);
269310
return Err(Error::Opte(opte_ioctl::Error::InvalidArgument(
270-
String::from("There must be at least two underlay NICs"),
311+
String::from(MESSAGE),
271312
)));
272313
}
273314
for nic in &underlay_nics {
@@ -294,5 +335,8 @@ fn find_chelsio_links() -> Result<Vec<PhysicalLink>, Error> {
294335
// `Dladm` to get the real Chelsio links on a Gimlet. These will likely be
295336
// called `cxgbeN`, but we explicitly call them `netN` to be clear that
296337
// they're likely VNICs for the time being.
297-
Ok((0..2).map(|i| PhysicalLink(format!("net{}", i))).collect())
338+
Ok(XDE_VNIC_NAMES
339+
.into_iter()
340+
.map(|name| PhysicalLink(name.to_string()))
341+
.collect())
298342
}

sled-agent/src/sled_agent.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! Sled agent implementation
66
77
use crate::config::Config;
8+
use crate::illumos::vnic::VnicKind;
89
use crate::illumos::zfs::{
910
Mountpoint, ZONE_ZFS_DATASET, ZONE_ZFS_DATASET_MOUNTPOINT,
1011
};
@@ -148,7 +149,7 @@ impl SledAgent {
148149
// to leave the running Zones intact).
149150
let zones = Zones::get()?;
150151
for z in zones {
151-
warn!(log, "Deleting zone: {}", z.name());
152+
warn!(log, "Deleting existing zone"; "zone_name" => z.name());
152153
Zones::halt_and_remove_logged(&log, z.name())?;
153154
}
154155

@@ -162,18 +163,28 @@ impl SledAgent {
162163
// This should be accessible via:
163164
// $ dladm show-linkprop -c -p zone -o LINK,VALUE
164165
//
165-
// Note that this currently deletes only VNICs that start with the
166-
// prefix the sled-agent uses. We'll need to generate an alert or
167-
// otherwise handle VNICs that we _don't_ expect.
168-
let vnics = Dladm::get_vnics()?;
169-
for vnic in vnics
170-
.iter()
171-
.filter(|vnic| vnic.starts_with(crate::illumos::dladm::VNIC_PREFIX))
172-
{
173-
warn!(log, "Deleting VNIC: {}", vnic);
166+
// Note that we don't currently delete the VNICs in any particular
167+
// order. That should be OK, since we're definitely deleting the guest
168+
// VNICs before the xde devices, which is the main constraint.
169+
for vnic in Dladm::get_vnics()? {
170+
warn!(
171+
log,
172+
"Deleting existing VNIC";
173+
"vnic_name" => &vnic,
174+
"vnic_kind" => ?VnicKind::from_name(&vnic).unwrap(),
175+
);
174176
Dladm::delete_vnic(&vnic)?;
175177
}
176178

179+
// Also delete any extant xde devices. These should also eventually be
180+
// recovered / tracked, to avoid interruption of any guests that are
181+
// still running. That's currently irrelevant, since we're deleting the
182+
// zones anyway.
183+
//
184+
// This is also tracked by
185+
// https://github.com/oxidecomputer/omicron/issues/725.
186+
crate::opte::delete_all_xde_devices(&log)?;
187+
177188
let storage = StorageManager::new(
178189
&log,
179190
*id,

0 commit comments

Comments
 (0)