Skip to content

Commit d4bc251

Browse files
authored
Derive vnic device names/backends from PCI path (#204)
Two instance specs' network device and backend names must agree for the specs to be migration-compatible. Today, the spec generator derives these names from the name of the host vNIC to which the instance should bind, which name can and will change over a migration. Instead, generate network device/backend names from the NIC's PCI path, which is already not allowed to change during migration.
1 parent adfd92e commit d4bc251

2 files changed

Lines changed: 28 additions & 12 deletions

File tree

  • bin/propolis-server/src/lib
  • crates/propolis-types/src

bin/propolis-server/src/lib/spec.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ fn slot_to_pci_path(
7979
.map_err(|_| SpecBuilderError::PciSlotInvalid(slot.0, ty))
8080
}
8181

82+
/// Generates NIC device and backend names from the NIC's PCI path. This is
83+
/// needed because the `name` field in a propolis-client
84+
/// `NetworkInterfaceRequest` is actually the name of the host vNIC to bind to,
85+
/// and that can change between incarnations of an instance. The PCI path is
86+
/// unique to each NIC but must remain stable over a migration, so it's suitable
87+
/// for use in this naming scheme.
88+
///
89+
/// N.B. Migrating a NIC requires the source and target to agree on these names,
90+
/// so changing this routine's behavior will prevent Propolis processes
91+
/// with the old behavior from migrating processes with the new behavior.
92+
fn pci_path_to_nic_names(path: PciPath) -> (String, String) {
93+
(format!("vnic-{}", path), format!("vnic-{}-backend", path))
94+
}
95+
8296
/// A helper for building instance specs out of component parts.
8397
pub struct SpecBuilder {
8498
spec: InstanceSpec,
@@ -138,11 +152,12 @@ impl SpecBuilder {
138152
let pci_path = slot_to_pci_path(nic.slot, SlotType::NIC)?;
139153
self.register_pci_device(pci_path)?;
140154

155+
let (device_name, backend_name) = pci_path_to_nic_names(pci_path);
141156
if self
142157
.spec
143158
.network_backends
144159
.insert(
145-
nic.name.to_string(),
160+
backend_name.clone(),
146161
NetworkBackend {
147162
kind: NetworkBackendKind::Virtio {
148163
vnic_name: nic.name.to_string(),
@@ -159,10 +174,7 @@ impl SpecBuilder {
159174
if self
160175
.spec
161176
.network_devices
162-
.insert(
163-
nic.name.to_string(),
164-
NetworkDevice { backend_name: nic.name.to_string(), pci_path },
165-
)
177+
.insert(device_name, NetworkDevice { backend_name, pci_path })
166178
.is_some()
167179
{
168180
return Err(SpecBuilderError::DeviceNameInUse(
@@ -405,11 +417,12 @@ impl SpecBuilder {
405417
))
406418
})?;
407419

420+
let (device_name, backend_name) = pci_path_to_nic_names(pci_path);
408421
if self
409422
.spec
410423
.network_backends
411424
.insert(
412-
vnic_name.to_string(),
425+
backend_name.clone(),
413426
NetworkBackend {
414427
kind: NetworkBackendKind::Virtio {
415428
vnic_name: vnic_name.to_string(),
@@ -426,10 +439,7 @@ impl SpecBuilder {
426439
if self
427440
.spec
428441
.network_devices
429-
.insert(
430-
name.to_string(),
431-
NetworkDevice { backend_name: vnic_name.to_string(), pci_path },
432-
)
442+
.insert(device_name, NetworkDevice { backend_name, pci_path })
433443
.is_some()
434444
{
435445
return Err(SpecBuilderError::DeviceNameInUse(name.to_string()));

crates/propolis-types/src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! can all use those types (and implement their own conversions to/from them)
66
//! without any layering oddities.
77
8+
use std::fmt::Display;
89
use std::io::{Error, ErrorKind};
910
use std::str::FromStr;
1011

@@ -93,13 +94,18 @@ impl FromStr for PciPath {
9394
}
9495
}
9596

97+
impl Display for PciPath {
98+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99+
write!(f, "{}.{}.{}", self.0, self.1, self.2)
100+
}
101+
}
102+
96103
impl Serialize for PciPath {
97104
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
98105
where
99106
S: Serializer,
100107
{
101-
serializer
102-
.serialize_str(format!("{}.{}.{}", self.0, self.1, self.2).as_str())
108+
serializer.serialize_str(format!("{}", self).as_str())
103109
}
104110
}
105111

0 commit comments

Comments
 (0)