Skip to content

Commit 2502938

Browse files
authored
Merge branch 'main' into feat/7a_onboarded
2 parents 9ae14ca + 7c7884c commit 2502938

File tree

11 files changed

+211
-4
lines changed

11 files changed

+211
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to
1212

1313
- [#5065](https://github.com/firecracker-microvm/firecracker/pull/5065) Added
1414
support for Intel AMX (Advanced Matrix Extensions).
15+
- [#4731](https://github.com/firecracker-microvm/firecracker/pull/4731): Added
16+
support for modifying the host TAP device name during snapshot restore.
1517

1618
### Changed
1719

docs/snapshotting/network-for-clones.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,63 @@ Otherwise, packets originating from the guest might be using old Link Layer
142142
Address for up to arp cache timeout seconds. After said timeout period,
143143
connectivity will work both ways even without an explicit flush.
144144

145+
### Renaming host device names
146+
147+
In some environments where the jailer is not being used, restoring a snapshot
148+
may be tricky because the tap device on the host will not be the same as the tap
149+
device that the original VM was mapped to when it was snapshotted, for example
150+
when the tap device comes from a pool of such devices.
151+
152+
In this case you can use the `network_overrides` parameter of the snapshot
153+
restore API to specify which guest network device maps to which host tap device.
154+
155+
For example, if we have a network interface named `eth0` in the snapshotted
156+
microVM, we can override it to point to the host device `vmtap01` during
157+
snapshot resume, like this:
158+
159+
```bash
160+
curl --unix-socket /tmp/firecracker.socket -i \
161+
-X PUT 'http://localhost/snapshot/load' \
162+
-H 'Accept: application/json' \
163+
-H 'Content-Type: application/json' \
164+
-d '{
165+
"snapshot_path": "./snapshot_file",
166+
"mem_backend": {
167+
"backend_path": "./mem_file",
168+
"backend_type": "File"
169+
},
170+
"network_overrides": [
171+
{
172+
"iface_id": "eth0",
173+
"host_dev_name": "vmtap01"
174+
}
175+
]
176+
}'
177+
```
178+
179+
This may require reconfiguration of the networking inside the VM so that it is
180+
still routable externally.
181+
[network setup documentation](../network-setup.md#in-the-guest) describes what
182+
the typical setup is. If you are not using network namespaces or the jailer,
183+
then the guest will have to be made aware (via vsock or other channel) that it
184+
needs to reconfigure its network to match the network configured on the tap
185+
device.
186+
187+
If the new TAP device, say `vmtap3` has been configured to use a guest address
188+
of `172.16.3.2` then after snapshot restore you would run something like:
189+
190+
```bash
191+
# In the guest
192+
193+
# Clear out the previous addr and route
194+
ip addr flush dev eth0
195+
ip route flush dev eth0
196+
197+
# Configure the new address
198+
ip addr add 172.16.3.2/30 dev eth0
199+
ip route add default via 172.16.3.1/30 dev eth0
200+
```
201+
145202
# Ingress connectivity
146203

147204
The above setup only provides egress connectivity. If in addition we also want

src/firecracker/src/api_server/request/snapshot.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ fn parse_put_snapshot_load(body: &Body) -> Result<ParsedRequest, RequestError> {
105105
mem_backend,
106106
enable_diff_snapshots: snapshot_config.enable_diff_snapshots,
107107
resume_vm: snapshot_config.resume_vm,
108+
network_overrides: snapshot_config.network_overrides,
108109
};
109110

110111
// Construct the `ParsedRequest` object.
@@ -120,7 +121,7 @@ fn parse_put_snapshot_load(body: &Body) -> Result<ParsedRequest, RequestError> {
120121

121122
#[cfg(test)]
122123
mod tests {
123-
use vmm::vmm_config::snapshot::{MemBackendConfig, MemBackendType};
124+
use vmm::vmm_config::snapshot::{MemBackendConfig, MemBackendType, NetworkOverride};
124125

125126
use super::*;
126127
use crate::api_server::parsed_request::tests::{depr_action_from_req, vmm_action_from_request};
@@ -181,6 +182,7 @@ mod tests {
181182
},
182183
enable_diff_snapshots: false,
183184
resume_vm: false,
185+
network_overrides: vec![],
184186
};
185187
let mut parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
186188
assert!(
@@ -210,6 +212,7 @@ mod tests {
210212
},
211213
enable_diff_snapshots: true,
212214
resume_vm: false,
215+
network_overrides: vec![],
213216
};
214217
let mut parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
215218
assert!(
@@ -239,6 +242,46 @@ mod tests {
239242
},
240243
enable_diff_snapshots: false,
241244
resume_vm: true,
245+
network_overrides: vec![],
246+
};
247+
let mut parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
248+
assert!(
249+
parsed_request
250+
.parsing_info()
251+
.take_deprecation_message()
252+
.is_none()
253+
);
254+
assert_eq!(
255+
vmm_action_from_request(parsed_request),
256+
VmmAction::LoadSnapshot(expected_config)
257+
);
258+
259+
let body = r#"{
260+
"snapshot_path": "foo",
261+
"mem_backend": {
262+
"backend_path": "bar",
263+
"backend_type": "Uffd"
264+
},
265+
"resume_vm": true,
266+
"network_overrides": [
267+
{
268+
"iface_id": "eth0",
269+
"host_dev_name": "vmtap2"
270+
}
271+
]
272+
}"#;
273+
let expected_config = LoadSnapshotParams {
274+
snapshot_path: PathBuf::from("foo"),
275+
mem_backend: MemBackendConfig {
276+
backend_path: PathBuf::from("bar"),
277+
backend_type: MemBackendType::Uffd,
278+
},
279+
enable_diff_snapshots: false,
280+
resume_vm: true,
281+
network_overrides: vec![NetworkOverride {
282+
iface_id: String::from("eth0"),
283+
host_dev_name: String::from("vmtap2"),
284+
}],
242285
};
243286
let mut parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
244287
assert!(
@@ -265,6 +308,7 @@ mod tests {
265308
},
266309
enable_diff_snapshots: false,
267310
resume_vm: true,
311+
network_overrides: vec![],
268312
};
269313
let parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
270314
assert_eq!(

src/firecracker/swagger/firecracker.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,24 @@ definitions:
12161216
Type of snapshot to create. It is optional and by default, a full
12171217
snapshot is created.
12181218

1219+
NetworkOverride:
1220+
type: object
1221+
description:
1222+
Allows for changing the backing TAP device of a network interface
1223+
during snapshot restore.
1224+
required:
1225+
- iface_id
1226+
- host_dev_name
1227+
properties:
1228+
iface_id:
1229+
type: string
1230+
description:
1231+
The name of the interface to modify
1232+
host_dev_name:
1233+
type: string
1234+
description:
1235+
The new host device of the interface
1236+
12191237
SnapshotLoadParams:
12201238
type: object
12211239
description:
@@ -1247,6 +1265,12 @@ definitions:
12471265
type: boolean
12481266
description:
12491267
When set to true, the vm is also resumed if the snapshot load is successful.
1268+
network_overrides:
1269+
type: array
1270+
description: Network host device names to override
1271+
items:
1272+
$ref: "#/definitions/NetworkOverride"
1273+
12501274

12511275
TokenBucket:
12521276
type: object

src/vmm/src/devices/virtio/net/persist.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ impl RxBufferState {
5555
/// at snapshot.
5656
#[derive(Debug, Clone, Serialize, Deserialize)]
5757
pub struct NetState {
58-
id: String,
59-
tap_if_name: String,
58+
pub id: String,
59+
pub tap_if_name: String,
6060
rx_rate_limiter_state: RateLimiterState,
6161
tx_rate_limiter_state: RateLimiterState,
6262
/// The associated MMDS network stack.

src/vmm/src/persist.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,21 @@ pub fn restore_from_snapshot(
405405
params: &LoadSnapshotParams,
406406
vm_resources: &mut VmResources,
407407
) -> Result<Arc<Mutex<Vmm>>, RestoreFromSnapshotError> {
408-
let microvm_state = snapshot_state_from_file(&params.snapshot_path)?;
408+
let mut microvm_state = snapshot_state_from_file(&params.snapshot_path)?;
409+
for entry in &params.network_overrides {
410+
let net_devices = &mut microvm_state.device_states.net_devices;
411+
if let Some(device) = net_devices
412+
.iter_mut()
413+
.find(|x| x.device_state.id == entry.iface_id)
414+
{
415+
device
416+
.device_state
417+
.tap_if_name
418+
.clone_from(&entry.host_dev_name);
419+
} else {
420+
return Err(SnapshotStateFromFileError::UnknownNetworkDevice.into());
421+
}
422+
}
409423
let track_dirty_pages = params.enable_diff_snapshots;
410424

411425
let vcpu_count = microvm_state
@@ -480,6 +494,8 @@ pub enum SnapshotStateFromFileError {
480494
Meta(std::io::Error),
481495
/// Failed to load snapshot state from file: {0}
482496
Load(#[from] crate::snapshot::SnapshotError),
497+
/// Unknown Network Device.
498+
UnknownNetworkDevice,
483499
}
484500

485501
fn snapshot_state_from_file(

src/vmm/src/rpc_interface.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,7 @@ mod tests {
12691269
},
12701270
enable_diff_snapshots: false,
12711271
resume_vm: false,
1272+
network_overrides: vec![],
12721273
},
12731274
)));
12741275
check_unsupported(runtime_request(VmmAction::SetEntropyDevice(

src/vmm/src/vmm_config/snapshot.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ pub struct CreateSnapshotParams {
4747
pub mem_file_path: PathBuf,
4848
}
4949

50+
/// Allows for changing the mapping between tap devices and host devices
51+
/// during snapshot restore
52+
#[derive(Debug, PartialEq, Eq, Deserialize)]
53+
pub struct NetworkOverride {
54+
/// The index of the interface to modify
55+
pub iface_id: String,
56+
/// The new name of the interface to be assigned
57+
pub host_dev_name: String,
58+
}
59+
5060
/// Stores the configuration that will be used for loading a snapshot.
5161
#[derive(Debug, PartialEq, Eq)]
5262
pub struct LoadSnapshotParams {
@@ -60,6 +70,8 @@ pub struct LoadSnapshotParams {
6070
/// When set to true, the vm is also resumed if the snapshot load
6171
/// is successful.
6272
pub resume_vm: bool,
73+
/// The network devices to override on load.
74+
pub network_overrides: Vec<NetworkOverride>,
6375
}
6476

6577
/// Stores the configuration for loading a snapshot that is provided by the user.
@@ -82,6 +94,9 @@ pub struct LoadSnapshotConfig {
8294
/// Whether or not to resume the vm post snapshot load.
8395
#[serde(default)]
8496
pub resume_vm: bool,
97+
/// The network devices to override on load.
98+
#[serde(default)]
99+
pub network_overrides: Vec<NetworkOverride>,
85100
}
86101

87102
/// Stores the configuration used for managing snapshot memory.

src/vmm/tests/integration_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ fn verify_load_snapshot(snapshot_file: TempFile, memory_file: TempFile) {
261261
},
262262
enable_diff_snapshots: false,
263263
resume_vm: true,
264+
network_overrides: vec![],
264265
}))
265266
.unwrap();
266267

@@ -344,6 +345,7 @@ fn verify_load_snap_disallowed_after_boot_resources(res: VmmAction, res_name: &s
344345
},
345346
enable_diff_snapshots: false,
346347
resume_vm: false,
348+
network_overrides: vec![],
347349
});
348350
let err = preboot_api_controller.handle_preboot_request(req);
349351
assert!(

tests/framework/microvm.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,7 @@ def restore_from_snapshot(
974974
snapshot: Snapshot,
975975
resume: bool = False,
976976
uffd_path: Path = None,
977+
rename_interfaces: dict = None,
977978
):
978979
"""Restore a snapshot"""
979980
jailed_snapshot = snapshot.copy_to_chroot(Path(self.chroot()))
@@ -1001,11 +1002,27 @@ def restore_from_snapshot(
10011002
# Adjust things just in case
10021003
self.kernel_file = Path(self.kernel_file)
10031004

1005+
iface_overrides = []
1006+
if rename_interfaces is not None:
1007+
iface_overrides = [
1008+
{"iface_id": k, "host_dev_name": v}
1009+
for k, v in rename_interfaces.items()
1010+
]
1011+
1012+
optional_kwargs = {}
1013+
if iface_overrides:
1014+
# For backwards compatibility ab testing we want to avoid adding
1015+
# new parameters until we have a release baseline with the new
1016+
# parameter. Once the release baseline has moved, this assignment
1017+
# can be inline in the snapshot_load command below
1018+
optional_kwargs["network_overrides"] = iface_overrides
1019+
10041020
self.api.snapshot_load.put(
10051021
mem_backend=mem_backend,
10061022
snapshot_path=str(jailed_vmstate),
10071023
enable_diff_snapshots=snapshot.is_diff,
10081024
resume_vm=resume,
1025+
**optional_kwargs,
10091026
)
10101027
# This is not a "wait for boot", but rather a "VM still works after restoration"
10111028
if snapshot.net_ifaces and resume:

0 commit comments

Comments
 (0)