Skip to content

Commit

Permalink
Tests for snapshot network renames
Browse files Browse the repository at this point in the history
Test that we can correctly parse configuration and API calls in a
backwards compatible way.

Signed-off-by: Andrew Laucius <[email protected]>
  • Loading branch information
andrewla committed Aug 26, 2024
1 parent 285d065 commit 6bb709b
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 5 deletions.
43 changes: 42 additions & 1 deletion src/firecracker/src/api_server/request/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ fn parse_put_snapshot_load(body: &Body) -> Result<ParsedRequest, RequestError> {

#[cfg(test)]
mod tests {
use vmm::vmm_config::snapshot::{MemBackendConfig, MemBackendType};
use vmm::vmm_config::snapshot::{MemBackendConfig, MemBackendType, NetworkOverride};

use super::*;
use crate::api_server::parsed_request::tests::{depr_action_from_req, vmm_action_from_request};
Expand Down Expand Up @@ -182,6 +182,7 @@ mod tests {
},
enable_diff_snapshots: false,
resume_vm: false,
network_overrides: vec![],
};
let mut parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
assert!(parsed_request
Expand Down Expand Up @@ -209,6 +210,7 @@ mod tests {
},
enable_diff_snapshots: true,
resume_vm: false,
network_overrides: vec![],
};
let mut parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
assert!(parsed_request
Expand Down Expand Up @@ -236,6 +238,44 @@ mod tests {
},
enable_diff_snapshots: false,
resume_vm: true,
network_overrides: vec![],
};
let mut parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
assert!(parsed_request
.parsing_info()
.take_deprecation_message()
.is_none());
assert_eq!(
vmm_action_from_request(parsed_request),
VmmAction::LoadSnapshot(expected_config)
);

let body = r#"{
"snapshot_path": "foo",
"mem_backend": {
"backend_path": "bar",
"backend_type": "Uffd"
},
"resume_vm": true,
"network_overrides": [
{
"iface_id": "eth0",
"host_dev_name": "vmtap2"
}
]
}"#;
let expected_config = LoadSnapshotParams {
snapshot_path: PathBuf::from("foo"),
mem_backend: MemBackendConfig {
backend_path: PathBuf::from("bar"),
backend_type: MemBackendType::Uffd,
},
enable_diff_snapshots: false,
resume_vm: true,
network_overrides: vec![NetworkOverride {
iface_id: String::from("eth0"),
host_dev_name: String::from("vmtap2"),
}],
};
let mut parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
assert!(parsed_request
Expand All @@ -260,6 +300,7 @@ mod tests {
},
enable_diff_snapshots: false,
resume_vm: true,
network_overrides: vec![],
};
let parsed_request = parse_put_snapshot(&Body::new(body), Some("load")).unwrap();
assert_eq!(
Expand Down
5 changes: 4 additions & 1 deletion src/vmm/src/persist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,10 @@ pub fn restore_from_snapshot(
.iter_mut()
.find(|x| x.device_state.id == entry.iface_id)
{
device.device_state.tap_if_name = entry.host_dev_name.clone();
device
.device_state
.tap_if_name
.clone_from(&entry.host_dev_name);
} else {
return Err(BuildMicrovmFromSnapshotError::UnknownNetworkDevice.into());
}
Expand Down
15 changes: 12 additions & 3 deletions tests/framework/microvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from enum import Enum
from functools import lru_cache
from pathlib import Path
from typing import Optional
from typing import Dict, Optional

from tenacity import retry, stop_after_attempt, wait_fixed

Expand Down Expand Up @@ -925,6 +925,7 @@ def restore_from_snapshot(
snapshot: Snapshot,
resume: bool = False,
uffd_path: Path = None,
rename_interfaces: Dict[str, str] = None,
):
"""Restore a snapshot"""
jailed_snapshot = snapshot.copy_to_chroot(Path(self.chroot()))
Expand All @@ -947,11 +948,19 @@ def restore_from_snapshot(
if uffd_path is not None:
mem_backend = {"backend_type": "Uffd", "backend_path": str(uffd_path)}

iface_overrides = []
if rename_interfaces:
iface_overrides = [
{"iface_id": k, "host_dev_name": v}
for k, v in rename_interfaces.items()
]

self.api.snapshot_load.put(
mem_backend=mem_backend,
snapshot_path=str(jailed_vmstate),
enable_diff_snapshots=snapshot.is_diff,
resume_vm=resume,
network_overrides=iface_overrides,
)
return jailed_snapshot

Expand Down Expand Up @@ -992,13 +1001,13 @@ def thread_backtraces(self):
)
return "\n".join(backtraces)

def wait_for_up(self, timeout=10):
def wait_for_up(self, timeout=10, iface=0):
"""Wait for guest running inside the microVM to come up and respond.
:param timeout: seconds to wait.
"""
try:
rc, stdout, stderr = self.ssh.run("true", timeout)
rc, stdout, stderr = self.ssh_iface(iface).run("true", timeout)
except subprocess.TimeoutExpired:
print(
f"Remote command did not respond within {timeout}s\n\n"
Expand Down
30 changes: 30 additions & 0 deletions tests/integration_tests/functional/test_snapshot_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
"""Basic tests scenarios for snapshot save/restore."""

import dataclasses
import filecmp
import logging
import os
Expand All @@ -13,6 +14,7 @@
import pytest

import host_tools.drive as drive_tools
import host_tools.network as net_tools
from framework.microvm import SnapshotType
from framework.utils import check_filesystem, check_output
from framework.utils_vsock import (
Expand Down Expand Up @@ -577,3 +579,31 @@ def test_vmgenid(guest_kernel_linux_6_1, rootfs, microvm_factory, snapshot_type)

# Update the base for next iteration
base_snapshot = snapshot


def test_snapshot_rename_interface(uvm_nano, microvm_factory):
"""
Test that we can restore a snapshot and point its interface to a
different host interface.
"""
base_iface = net_tools.NetIfaceConfig.with_id(0)

vm = uvm_nano
iface1 = dataclasses.replace(base_iface, tap_name="tap1")
vm.add_net_iface(iface=iface1)
# Create an interface but don't attach it to the device
vm.start()
vm.wait_for_up()

snapshot = vm.snapshot_full()

restored_vm = microvm_factory.build()
restored_vm.spawn()
iface2 = dataclasses.replace(base_iface, tap_name="tap2")
snapshot.net_ifaces.clear()
snapshot.net_ifaces.append(iface2)
restored_vm.restore_from_snapshot(
snapshot, rename_interfaces={base_iface.dev_name: "tap2"}
)
restored_vm.resume()
restored_vm.wait_for_up()

0 comments on commit 6bb709b

Please sign in to comment.