diff --git a/CHANGELOG.md b/CHANGELOG.md index 832e1f7d2ce..7304020cceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `VcpuExit::IoOut`. The average for these VM exits is not emitted since it can be deduced from the available emitted metrics. +### Changed + +- [#4230](https://github.com/firecracker-microvm/firecracker/pull/4230): + Changed microVM snapshot format version strategy. Firecracker snapshot format + now has a version that is independent of Firecracker version. The current + version of the snapshot format is v1.0.0. From now on, the Firecracker binary + will define the snapshot format version it supports and it will only be able + to load snapshots with format that is backwards compatible with that version. + Users can pass the `--snapshot-version` flag to the Firecracker binary to see + its supported snapshot version format. This change renders all previous + Firecracker snapshots (up to Firecracker version v1.6.0) incompatible with + the current Firecracker version. + ## [v1.6.0] ### Added diff --git a/Cargo.lock b/Cargo.lock index bbb53c559a0..32124eb40d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -711,10 +711,10 @@ dependencies = [ [[package]] name = "kvm-bindings" version = "0.7.0" -source = "git+https://github.com/firecracker-microvm/kvm-bindings?tag=v0.7.0-1#93af344a93b83b39cdfea0d0a860b3b57d29d28b" +source = "git+https://github.com/firecracker-microvm/kvm-bindings?tag=v0.7.0-2#60cc04e2658646516f4e763eca77fbfa1cf5ec1f" dependencies = [ - "versionize", - "versionize_derive", + "serde", + "serde-big-array", "vmm-sys-util", ] @@ -1130,6 +1130,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.195" @@ -1177,13 +1186,14 @@ checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" name = "snapshot" version = "0.1.0" dependencies = [ - "criterion", + "bincode", + "crc64", "displaydoc", "libc", "log-instrument", + "semver", + "serde", "thiserror", - "versionize", - "versionize_derive", ] [[package]] @@ -1195,6 +1205,7 @@ dependencies = [ "displaydoc", "libc", "log-instrument", + "semver", "snapshot", "thiserror", "utils", @@ -1393,8 +1404,6 @@ dependencies = [ "serde", "serde_json", "thiserror", - "versionize", - "versionize_derive", "vm-memory 0.13.1", "vmm-sys-util", ] @@ -1414,34 +1423,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "versionize" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62929d59c7f6730b7298fcb363760550f4db6e353fbac4076d447d0e82799d6d" -dependencies = [ - "bincode", - "crc64", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn 1.0.109", - "versionize_derive", - "vmm-sys-util", -] - -[[package]] -name = "versionize_derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4971c07ef708cd51003222e509aaed8eae14aeb2907706b01578843195e03a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "vhost" version = "0.10.0" @@ -1533,8 +1514,6 @@ dependencies = [ "timerfd", "userfaultfd", "utils", - "versionize", - "versionize_derive", "vhost", "vm-allocator", "vm-fdt", @@ -1550,6 +1529,8 @@ checksum = "1d1435039746e20da4f8d507a72ee1b916f7b4b05af7a91c093d2c6561934ede" dependencies = [ "bitflags 1.3.2", "libc", + "serde", + "serde_derive", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d57f997764e..926d1ca330b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,4 @@ panic = "abort" lto = true [patch.crates-io] -kvm-bindings = { git = "https://github.com/firecracker-microvm/kvm-bindings", tag = "v0.7.0-1", features = ["fam-wrappers"] } +kvm-bindings = { git = "https://github.com/firecracker-microvm/kvm-bindings", tag = "v0.7.0-2", features = ["fam-wrappers"] } diff --git a/docs/images/version_graph.png b/docs/images/version_graph.png deleted file mode 100644 index 8b636d3791d..00000000000 Binary files a/docs/images/version_graph.png and /dev/null differ diff --git a/docs/images/versionize.png b/docs/images/versionize.png deleted file mode 100644 index 4f21be53055..00000000000 Binary files a/docs/images/versionize.png and /dev/null differ diff --git a/docs/snapshotting/snapshot-support.md b/docs/snapshotting/snapshot-support.md index 7e3b070ca6c..cf255701b5a 100644 --- a/docs/snapshotting/snapshot-support.md +++ b/docs/snapshotting/snapshot-support.md @@ -175,41 +175,13 @@ The snapshot functionality is still in developer preview due to the following: ## Snapshot versioning -The Firecracker snapshotting implementation offers support for snapshot versioning -(`cross-version snapshots`) in the following contexts: - -- Saving snapshots at older versions - - **DEPRECATED**: This feature is deprecated starting with version 1.5.0. It - will be removed in a subsequent release. After dropping support, Firecracker - will be able to create snapshots only for the version supported by the - Firecracker binary that launched the microVM and not for older versions. - - This refers to being able to create a snapshot with any version in the - `[N, N + o]` interval, while running Firecracker version `N+o`. - - The possibility to save snapshots at older versions might not be offered by - all Firecracker releases. Depending on the features that it introduces, a new - Firecracker release `v` might drop the possibility to save snapshots at any - versions older than `v`. - - For example Firecracker v1.0 and v1.1 adds support for some additional virtio - features (e.g. notification suppression). These features lead the guest - drivers to behave in a very specific way and as a consequence the Firecracker - devices have to respond accordingly. As a result, the snapshots that are - created while these features are in use will not be backwards compatible with - previous versions of Firecracker since the devices that come with these older - versions do not behave in a way that’s compatible with the snapshotted guest - drivers. - - The list of versions that break snapshot backwards compatibility: `1.0`, `1.1` -- Loading snapshots from older versions (being able to load a snapshot created - by any Firecracker version in the `[N, N + o]` interval, in a Firecracker - version `N+o`). - -The design supports an unlimited number of versions, the value of `o` (maximum number -of older versions that we can restore from / save a snapshot to, from the current -version) will be defined later. +The microVM state snapshot file uses a data format that has a version in the +form of `MAJOR.MINOR.PATCH`. Each Firecracker binary supports a fixed version +of the snapshot data format. When creating a snapshot, Firecracker will use the +supported data format version. When loading snapshots, Firecracker will check +that the snapshot version is compatible with the version it supports. More +information about the snapshot data format and details about snapshot data +format versions can be found at [versioning](./versioning.md). ## Snapshot API @@ -294,7 +266,6 @@ curl --unix-socket /tmp/firecracker.socket -i \ "snapshot_type": "Full", "snapshot_path": "./snapshot_file", "mem_file_path": "./mem_file", - "version": "1.0.0" }' ``` @@ -323,12 +294,6 @@ the snapshot. If they exist, the files will be truncated and overwritten. - If diff snapshots were enabled, the snapshot creation resets then the dirtied page bitmap and marks all pages clean (from a diff snapshot point of view). - - If a `version` is specified, the new snapshot is saved at that version, - otherwise it will be saved at the latest snapshot version of the running - Firecracker. The version is only used for the microVM state file as it - contains internal state structures for device emulation, vCPUs and others - that can change their format from a Firecracker version to another. - Versioning is not required for the block and memory files. - _on failure_: no side-effects. @@ -336,11 +301,6 @@ the snapshot. If they exist, the files will be truncated and overwritten. - The separate block device file components of the snapshot have to be handled by the user. -- If specified, `version` must match the firecracker version that introduced a - snapshot version, which may differ from the running Firecracker version. For - example, if you are running on `1.1.2` and want to target version `1.0.4`, you - should specify `1.0.0`. Not specifying `version` uses the latest snapshot - version available to that version. #### Creating diff snapshots @@ -358,7 +318,6 @@ curl --unix-socket /tmp/firecracker.socket -i \ "snapshot_type": "Diff", "snapshot_path": "./snapshot_file", "mem_file_path": "./mem_file", - "version": "1.0.0" }' ``` diff --git a/docs/snapshotting/versioning.md b/docs/snapshotting/versioning.md index b595a524e38..a04472269ae 100644 --- a/docs/snapshotting/versioning.md +++ b/docs/snapshotting/versioning.md @@ -1,44 +1,18 @@ # Firecracker snapshot versioning -This document describes how Firecracker persists its state across multiple -versions, diving deep into the snapshot format, encoding, compatibility and +This document describes how Firecracker persists microVM state into Firecracker +snapshots. It describes the snapshot format, encoding, compatibility and limitations. ## Introduction -The design behind the snapshot implementation enables version tolerant save -and restore across multiple Firecracker versions which we call a version space. -For example, one can pause a microVM, save it to disk with Firecracker version -**0.23.0** and later load it in Firecracker version **0.24.0**. It also works -in reverse: Firecracker version **0.23.0** loads what **0.24.0** saves. - -Below is an example graph showing backward and forward snapshot compatibility. -This is the general picture, but keep in mind that when adding new features -some version translations would not be possible. - -![Version graph]( -../images/version_graph.png?raw=true -"Version graph") - -A non-exhaustive list of how cross-version snapshot support can be used: - -Example scenario #1 - load snapshot from older version: - -* Start Firecracker v0.23 → Boot microVM → *Workload starts* → Pause → - CreateSnapshot(snap) → kill microVM -* Start Firecracker v0.24 → LoadSnapshot → Resume → *Workload continues* - -Example scenario #2 - load snapshot in older version: - -* Start Firecracker v0.24 → Boot microVM → *Workload starts* → Pause → - CreateSnapshot(snap, “0.23”) → kill microVM -* Start Firecracker v0.23 → LoadSnapshot(snap) → Resume → *Workload continues* - -Example scenario #3 - load snapshot in older version: - -* Start Firecracker v0.24 → LoadSnapshot(older_snap) → Resume → - *Workload continues* → Pause → CreateSnapshot(snap, “0.23”) → kill microVM -* Start Firecracker v0.23 → LoadSnapshot(snap) → Resume → *Workload continues* +Firecracker uses the serde crate [1] along with the bincode [2] format to +serialize its state into Firecracker snapshots. Firecracker snapshots have +versions that are independent of Firecracker versions. Each Firecracker version +declares support for a specific snapshot data format version. When creating a +snapshot, Firecracker will use the supported snapshot format version. When +loading a snapshot, Firecracker will check that format of the snapshot file is +compatible with the snapshot version Firecracker supports. ## Overview @@ -61,59 +35,32 @@ emulation, KVM and vCPUs) with 2 exceptions - serial emulation and vsock backend While we continuously improve and extend Firecracker's features by adding new capabilities, devices or enhancements, the microVM state file may change both -structurally and semantically with each new release. The state file includes -versioning information and each Firecracker release implements distinct -save/restore logic for the supported version space. +structurally and semantically with each new release. ## MicroVM state file format -A microVM state file is further split into four different fields: +A Firecracker snapshot has the following format: -| Field | Bits| Description | -|----|----|----| -| magic_id | 64 | Firecracker snapshot, architecture (x86_64/aarch64) and storage version. | -| version | 16 | The snapshot version number internally mapped 1:1 to a specific Firecracker version. | +| Field | Bits | Description | +|-------|------|-------------| +| magic_id | 64 | Firecracker snapshot and architecture (x86_64/aarch64). | +| version | M | The snapshot data format version (`MAJOR.MINOR.PATCH`) | | state | N | Bincode blob containing the microVM state. | -| crc| 64 | Optional CRC64 sum of magic_id, version and state fields. | - -**Note**: the last 16 bits of `magic_id` encode the storage version which specifies -the encoding used for the `version` and `state` fields. The current -implementation sets this field to 1, which identifies it as a [Serde bincode](https://github.com/servo/bincode) -compatible encoder/decoder. - -### Version tolerant ser/de - -Firecracker reads and writes the `state` blob of the snapshot by using per -version, separate serialization and deserialization logic. This logic is mostly -autogenerated by a Rust procedural macro based on `struct` and `enum` -annotations. Basically, one can say that these structures support versioning. -The versioning logic is generated by parsing a structure's history log (encoded -using Rust annotations) and emitting Rust code. - -Versioned serialization and deserialization is divided into two translation layers: - -* field translator, -* semantic translator. - -The _field translator_ implements the logic to convert between different -versions of the same Rust POD structure: it can deserialize or serialize from -source version to target. -The translation is done field by field - the common fields are copied from -source to target, and the fields that are unique to the target are -(de)serialized with their default values. - -The _semantic translator_ is only concerned with translating the semantics of -the serialized/deserialized fields. - -The _field translator_ is generated automatically through a procedural macro, -and the _semantic translation methods_ have to be annotated in the structure -by the user. - -This block diagram illustrates the concept: - -![Versionize]( -../images/versionize.png?raw=true -"Versionize layers") +| crc | 64 | Optional CRC64 sum of magic_id, version and state fields. | + +The snapshot format has its own version encoded in the snapshot file itself +after the snapshot's `magic_id`. The snapshot format version is independent of +the Firecracker version and it is of the form `MAJOR.MINOR.PATCH`. + +Currently, Firecracker uses the [Serde bincode +encoder](https://github.com/servo/bincode) for serializing the microVM state. +The encoding format that bincode uses does not allow backwards compatible +changes in the state, so essentially every change in the microVM state +description will result in bump of the format's `MAJOR` version. If the needs +arises, we will look into alternative formats that allow more flexibility with +regards to backwards compatibility. If/when this happens, we will define how +changes in the snapshot format reflect to changes in its `MAJOR.MINOR.PATCH` +version. ## VM state encoding @@ -132,10 +79,6 @@ Key benefits of using *bincode*: The current implementation relies on the [Serde bincode encoder](https://github.com/servo/bincode). -Versionize is compatible to Serde with bincode backend: structures serialized -with versionize at a specific version can be deserialized with Serde. Also -structures serialized with serde can be deserialized with versionize. - ## Snapshot compatibility ### Host kernel @@ -195,18 +138,8 @@ specifically, the MSRs corresponding to the guest exposed features. ## Implementation -To enable Firecracker cross version snapshots we have designed and built two -crates: - -* [versionize](https://crates.io/crates/versionize) - defines the `Versionize` - trait, implements serialization of primitive types and provides a helper - class to map Firecracker versions to individual structure versions. -* [versionize_derive](https://crates.io/crates/versionize_derive) - exports - a procedural macro that consumes structures and enums and their annotations - to produce an implementation of the `Versionize` trait. - -The microVM state file format is implemented in the [snapshot crate](../../src/snapshot/src/lib.rs) -in the Firecracker repository. -All Firecracker devices implement the [Persist](../../src/snapshot/src/persist.rs) +The microVM state file format is implemented in the [snapshot +crate](../../src/snapshot/src/lib.rs) in the Firecracker repository. All +Firecracker devices implement the [Persist](../../src/snapshot/src/persist.rs) trait which exposes an interface that enables creating from and saving to the microVM state. diff --git a/src/firecracker/src/main.rs b/src/firecracker/src/main.rs index f21f79c182f..517a6c2abf1 100644 --- a/src/firecracker/src/main.rs +++ b/src/firecracker/src/main.rs @@ -24,9 +24,9 @@ use vmm::builder::StartMicrovmError; use vmm::logger::{ debug, error, info, LoggerConfig, ProcessTimeReporter, StoreMetric, LOGGER, METRICS, }; +use vmm::persist::SNAPSHOT_VERSION; use vmm::resources::VmResources; use vmm::signal_handler::register_signal_handlers; -use vmm::version_map::{FC_VERSION_TO_SNAP_VERSION, VERSION_MAP}; use vmm::vmm_config::instance_info::{InstanceInfo, VmState}; use vmm::vmm_config::metrics::{init_metrics, MetricsConfig, MetricsConfigError}; use vmm::{EventManager, FcExitCode, HTTP_MAX_PAYLOAD_SIZE}; @@ -228,10 +228,16 @@ fn main_exec() -> Result<(), MainError> { "Whether or not to load boot timer device for logging elapsed time since \ InstanceStart command.", )) - .arg(Argument::new("version").takes_value(false).help( - "Print the binary version number and a list of supported snapshot data format \ - versions.", - )) + .arg( + Argument::new("version") + .takes_value(false) + .help("Print the binary version number."), + ) + .arg( + Argument::new("snapshot-version") + .takes_value(false) + .help("Print the supported data format version."), + ) .arg( Argument::new("describe-snapshot") .takes_value(true) @@ -263,6 +269,11 @@ fn main_exec() -> Result<(), MainError> { return Ok(()); } + if arguments.flag_present("snapshot-version") { + println!("v{SNAPSHOT_VERSION}"); + return Ok(()); + } + if let Some(snapshot_path) = arguments.single_value("describe-snapshot") { print_snapshot_data_format(snapshot_path)?; return Ok(()); @@ -520,8 +531,6 @@ enum SnapshotVersionError { OpenSnapshot(io::Error), /// Invalid data format version of snapshot file: {0} SnapshotVersion(SnapshotError), - /// Cannot translate snapshot data version {0} to Firecracker microVM version - FirecrackerVersion(u16), } // Print data format of provided snapshot state file. @@ -529,15 +538,10 @@ fn print_snapshot_data_format(snapshot_path: &str) -> Result<(), SnapshotVersion let mut snapshot_reader = File::open(snapshot_path).map_err(SnapshotVersionError::OpenSnapshot)?; - let data_format_version = Snapshot::get_data_version(&mut snapshot_reader, &VERSION_MAP) + let data_format_version = Snapshot::get_format_version(&mut snapshot_reader) .map_err(SnapshotVersionError::SnapshotVersion)?; - let (key, _) = FC_VERSION_TO_SNAP_VERSION - .iter() - .find(|(_, &val)| val == data_format_version) - .ok_or_else(|| SnapshotVersionError::FirecrackerVersion(data_format_version))?; - - println!("v{}", key); + println!("v{}", data_format_version); Ok(()) } diff --git a/src/snapshot-editor/Cargo.toml b/src/snapshot-editor/Cargo.toml index 70ae45ffb71..f07302c6b93 100644 --- a/src/snapshot-editor/Cargo.toml +++ b/src/snapshot-editor/Cargo.toml @@ -14,6 +14,7 @@ clap = { version = "4.4.14", features = ["derive", "string"] } displaydoc = "0.2.4" libc = "0.2.151" log-instrument = { path = "../log-instrument", optional = true } +semver = "1.0.20" snapshot = { path = "../snapshot" } thiserror = "1.0.53" vmm = { path = "../vmm" } diff --git a/src/snapshot-editor/src/info.rs b/src/snapshot-editor/src/info.rs index 532f622e512..659c86e6b50 100644 --- a/src/snapshot-editor/src/info.rs +++ b/src/snapshot-editor/src/info.rs @@ -4,15 +4,13 @@ use std::path::PathBuf; use clap::Subcommand; +use semver::Version; use vmm::persist::MicrovmState; -use vmm::version_map::FC_VERSION_TO_SNAP_VERSION; use crate::utils::*; #[derive(Debug, thiserror::Error, displaydoc::Display)] pub enum InfoVmStateError { - /// Cannot translate snapshot data version {0} to Firecracker microVM version - InvalidVersion(u16), /// {0} Utils(#[from] UtilsError), } @@ -52,27 +50,19 @@ pub fn info_vmstate_command(command: InfoVmStateSubCommand) -> Result<(), InfoVm fn info( vmstate_path: &PathBuf, - f: impl Fn(&MicrovmState, u16) -> Result<(), InfoVmStateError>, + f: impl Fn(&MicrovmState, Version) -> Result<(), InfoVmStateError>, ) -> Result<(), InfoVmStateError> { let (vmstate, version) = open_vmstate(vmstate_path)?; f(&vmstate, version)?; Ok(()) } -fn info_version(_: &MicrovmState, version: u16) -> Result<(), InfoVmStateError> { - match FC_VERSION_TO_SNAP_VERSION - .iter() - .find(|(_, &v)| v == version) - { - Some((key, _)) => { - println!("v{key}"); - Ok(()) - } - None => Err(InfoVmStateError::InvalidVersion(version)), - } +fn info_version(_: &MicrovmState, version: Version) -> Result<(), InfoVmStateError> { + println!("v{version}"); + Ok(()) } -fn info_vcpu_states(state: &MicrovmState, _: u16) -> Result<(), InfoVmStateError> { +fn info_vcpu_states(state: &MicrovmState, _: Version) -> Result<(), InfoVmStateError> { for (i, state) in state.vcpu_states.iter().enumerate() { println!("vcpu {i}:"); println!("{state:#?}"); @@ -80,7 +70,7 @@ fn info_vcpu_states(state: &MicrovmState, _: u16) -> Result<(), InfoVmStateError Ok(()) } -fn info_vmstate(vmstate: &MicrovmState, _version: u16) -> Result<(), InfoVmStateError> { +fn info_vmstate(vmstate: &MicrovmState, _version: Version) -> Result<(), InfoVmStateError> { println!("{vmstate:#?}"); Ok(()) } diff --git a/src/snapshot-editor/src/utils.rs b/src/snapshot-editor/src/utils.rs index bf9be3ac5e2..625d7c58129 100644 --- a/src/snapshot-editor/src/utils.rs +++ b/src/snapshot-editor/src/utils.rs @@ -5,9 +5,9 @@ use std::fs::{File, OpenOptions}; use std::path::PathBuf; use fc_utils::u64_to_usize; +use semver::Version; use snapshot::Snapshot; use vmm::persist::MicrovmState; -use vmm::version_map::VERSION_MAP; // Some errors are only used in aarch64 code #[allow(unused)] @@ -26,12 +26,11 @@ pub enum UtilsError { } #[allow(unused)] -pub fn open_vmstate(snapshot_path: &PathBuf) -> Result<(MicrovmState, u16), UtilsError> { - let version_map = VERSION_MAP.clone(); +pub fn open_vmstate(snapshot_path: &PathBuf) -> Result<(MicrovmState, Version), UtilsError> { let mut snapshot_reader = File::open(snapshot_path).map_err(UtilsError::VmStateFileOpen)?; let metadata = std::fs::metadata(snapshot_path).map_err(UtilsError::VmStateFileMeta)?; let snapshot_len = u64_to_usize(metadata.len()); - Snapshot::load(&mut snapshot_reader, snapshot_len, version_map).map_err(UtilsError::VmStateLoad) + Snapshot::load(&mut snapshot_reader, snapshot_len).map_err(UtilsError::VmStateLoad) } // This method is used only in aarch64 code so far @@ -39,16 +38,15 @@ pub fn open_vmstate(snapshot_path: &PathBuf) -> Result<(MicrovmState, u16), Util pub fn save_vmstate( microvm_state: MicrovmState, output_path: &PathBuf, - version: u16, + version: Version, ) -> Result<(), UtilsError> { - let version_map = VERSION_MAP.clone(); let mut output_file = OpenOptions::new() .create(true) .write(true) .truncate(true) .open(output_path) .map_err(UtilsError::OutputFileOpen)?; - let mut snapshot = Snapshot::new(version_map, version); + let mut snapshot = Snapshot::new(version); snapshot .save(&mut output_file, µvm_state) .map_err(UtilsError::VmStateSave)?; diff --git a/src/snapshot/Cargo.toml b/src/snapshot/Cargo.toml index 0d0e578f482..cc37d7c23f6 100644 --- a/src/snapshot/Cargo.toml +++ b/src/snapshot/Cargo.toml @@ -11,18 +11,13 @@ bench = false [dependencies] libc = "0.2.117" -versionize = "0.2.0" -versionize_derive = "0.1.6" thiserror = "1.0.32" displaydoc = "0.2.4" log-instrument = { path = "../log-instrument", optional = true } - -[dev-dependencies] -criterion = { version = "0.5.0", default-features = false } - -[[bench]] -name = "version_map" -harness = false +semver = { version = "1.0.20", features = ["serde"] } +serde = { version = "1.0.192", features = ["derive"] } +bincode = "1.3.3" +crc64 = "2.0.0" [features] tracing = ["log-instrument"] diff --git a/src/snapshot/benches/version_map.rs b/src/snapshot/benches/version_map.rs deleted file mode 100644 index ea3c38b86d7..00000000000 --- a/src/snapshot/benches/version_map.rs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use snapshot::Snapshot; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; - -#[derive(Clone, Debug, Default, Versionize)] -struct Test { - a: Vec, - #[version(start = 1)] - b: u64, - #[version(start = 2)] - c: u64, - #[version(start = 3)] - d: u32, - #[version(start = 4)] - e: Vec, -} - -#[derive(Clone, Debug, Default, Versionize)] -struct Dummy { - a: String, - #[version(start = 2)] - b: [u64; 32], -} - -#[inline] -fn restore(mut snapshot_mem: &[u8], vm: VersionMap) { - Snapshot::unchecked_load::<&[u8], Test>(&mut snapshot_mem, vm).unwrap(); -} - -#[inline] -fn save(mut snapshot_mem: &mut W, vm: VersionMap) { - let state = Test { - a: vec![ - Dummy { - a: "a string".to_owned(), - b: [0x1234u64; 32] - }; - 200 - ], - b: 0, - c: 1, - d: 2, - e: vec![0x4321; 100], - }; - - let mut snapshot = Snapshot::new(vm.clone(), vm.latest_version()); - snapshot - .save_without_crc(&mut snapshot_mem, &state) - .unwrap(); -} - -pub fn criterion_benchmark(c: &mut Criterion) { - let mut snapshot_mem = vec![0u8; 1024 * 1024 * 128]; - let mut vm = VersionMap::new(); - - vm.new_version() - .set_type_version(Test::type_id(), 2) - .new_version() - .set_type_version(Test::type_id(), 3) - .new_version() - .set_type_version(Test::type_id(), 4) - .set_type_version(Dummy::type_id(), 2); - - let mut slice = &mut snapshot_mem.as_mut_slice(); - save(&mut slice, vm.clone()); - let snapshot_len = slice.as_ptr() as usize - snapshot_mem.as_slice().as_ptr() as usize; - println!("Snapshot length: {} bytes", snapshot_len); - - c.bench_function("Serialize in vspace=4", |b| { - b.iter(|| { - save( - black_box(&mut snapshot_mem.as_mut_slice()), - black_box(vm.clone()), - ) - }) - }); - - c.bench_function("Deserialize in vspace=4", |b| { - b.iter(|| restore(black_box(snapshot_mem.as_slice()), black_box(vm.clone()))) - }); - - // Extend vspace to 100. - for _ in 0..96 { - vm.new_version(); - } - - save(&mut snapshot_mem.as_mut_slice(), vm.clone()); - - c.bench_function("Serialize in vspace=100", |b| { - b.iter(|| { - save( - black_box(&mut snapshot_mem.as_mut_slice()), - black_box(vm.clone()), - ) - }) - }); - c.bench_function("Deserialize in vspace=100", |b| { - b.iter(|| restore(black_box(snapshot_mem.as_slice()), black_box(vm.clone()))) - }); - - // Extend vspace to 1001. - for _ in 0..900 { - vm.new_version(); - } - - // Save the snapshot at version 1001. - save(&mut snapshot_mem.as_mut_slice(), vm.clone()); - - c.bench_function("Serialize in vspace=1000", |b| { - b.iter(|| { - save( - black_box(&mut snapshot_mem.as_mut_slice()), - black_box(vm.clone()), - ) - }) - }); - c.bench_function("Deserialize in vspace=1000", |b| { - b.iter(|| restore(black_box(snapshot_mem.as_slice()), black_box(vm.clone()))) - }); -} - -criterion_group! { - name = benches; - config = Criterion::default().sample_size(200).noise_threshold(0.05); - targets = criterion_benchmark -} - -criterion_main! { - benches -} diff --git a/src/snapshot/src/crc.rs b/src/snapshot/src/crc.rs new file mode 100644 index 00000000000..e80c916777b --- /dev/null +++ b/src/snapshot/src/crc.rs @@ -0,0 +1,159 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Implements readers and writers that compute the CRC64 checksum of the bytes +//! read/written. + +use std::io::{Read, Write}; + +use crc64::crc64; + +/// Computes the CRC64 checksum of the read bytes. +/// +/// ``` +/// use std::io::Read; +/// +/// use snapshot::crc::CRC64Reader; +/// +/// let buf = vec![1, 2, 3, 4, 5]; +/// let mut read_buf = Vec::new(); +/// let mut slice = buf.as_slice(); +/// +/// // Create a reader from a slice. +/// let mut crc_reader = CRC64Reader::new(&mut slice); +/// +/// let count = crc_reader.read_to_end(&mut read_buf).unwrap(); +/// assert_eq!(crc_reader.checksum(), 0xFB04_60DE_0638_3654); +/// assert_eq!(read_buf, buf); +/// ``` +#[derive(Debug)] +pub struct CRC64Reader { + reader: T, + crc64: u64, +} + +impl CRC64Reader +where + T: Read, +{ + /// Create a new reader. + pub fn new(reader: T) -> Self { + CRC64Reader { crc64: 0, reader } + } + /// Returns the current checksum value. + pub fn checksum(&self) -> u64 { + self.crc64 + } +} + +impl Read for CRC64Reader +where + T: Read, +{ + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let bytes_read = self.reader.read(buf)?; + self.crc64 = crc64(self.crc64, &buf[..bytes_read]); + Ok(bytes_read) + } +} + +/// Computes the CRC64 checksum of the written bytes. +/// +/// ``` +/// use std::io::Write; +/// +/// use snapshot::crc::CRC64Writer; +/// +/// let mut buf = vec![0; 16]; +/// let write_buf = vec![123; 16]; +/// let mut slice = buf.as_mut_slice(); +/// +/// // Create a new writer from slice. +/// let mut crc_writer = CRC64Writer::new(&mut slice); +/// +/// crc_writer.write_all(&write_buf.as_slice()).unwrap(); +/// assert_eq!(crc_writer.checksum(), 0x29D5_3572_1632_6566); +/// assert_eq!(write_buf, buf); +/// ``` +#[derive(Debug)] +pub struct CRC64Writer { + writer: T, + crc64: u64, +} + +impl CRC64Writer +where + T: Write, +{ + /// Create a new writer. + pub fn new(writer: T) -> Self { + CRC64Writer { crc64: 0, writer } + } + + /// Returns the current checksum value. + pub fn checksum(&self) -> u64 { + self.crc64 + } +} + +impl Write for CRC64Writer +where + T: Write, +{ + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let bytes_written = self.writer.write(buf)?; + self.crc64 = crc64(self.crc64, &buf[..bytes_written]); + Ok(bytes_written) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.writer.flush() + } +} + +#[cfg(test)] +mod tests { + use super::{CRC64Reader, CRC64Writer, Read, Write}; + + #[test] + fn test_crc_new() { + let buf = vec![1; 5]; + let mut slice = buf.as_slice(); + let crc_reader = CRC64Reader::new(&mut slice); + assert_eq!(crc_reader.crc64, 0); + assert_eq!(crc_reader.reader, &[1; 5]); + assert_eq!(crc_reader.checksum(), 0); + + let mut buf = vec![0; 5]; + let mut slice = buf.as_mut_slice(); + let crc_writer = CRC64Writer::new(&mut slice); + assert_eq!(crc_writer.crc64, 0); + assert_eq!(crc_writer.writer, &[0; 5]); + assert_eq!(crc_writer.checksum(), 0); + } + + #[test] + fn test_crc_read() { + let buf = vec![1, 2, 3, 4, 5]; + let mut read_buf = vec![0; 16]; + + let mut slice = buf.as_slice(); + let mut crc_reader = CRC64Reader::new(&mut slice); + crc_reader.read_to_end(&mut read_buf).unwrap(); + assert_eq!(crc_reader.checksum(), 0xFB04_60DE_0638_3654); + assert_eq!(crc_reader.checksum(), crc_reader.crc64); + } + + #[test] + fn test_crc_write() { + let mut buf = vec![0; 16]; + let write_buf = vec![123; 16]; + + let mut slice = buf.as_mut_slice(); + let mut crc_writer = CRC64Writer::new(&mut slice); + crc_writer.write_all(write_buf.as_slice()).unwrap(); + crc_writer.flush().unwrap(); + assert_eq!(crc_writer.checksum(), 0x29D5_3572_1632_6566); + assert_eq!(crc_writer.checksum(), crc_writer.crc64); + } +} diff --git a/src/snapshot/src/lib.rs b/src/snapshot/src/lib.rs index 633e0c9f1fd..1072436f78a 100644 --- a/src/snapshot/src/lib.rs +++ b/src/snapshot/src/lib.rs @@ -1,47 +1,47 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![warn(missing_docs)] -//! Provides version tolerant serialization and deserialization facilities and -//! implements a persistent storage format for Firecracker state snapshots. +//! Provides serialization and deserialization facilities and implements a persistent storage +//! format for Firecracker state snapshots. //! //! The `Snapshot` API manages serialization and deserialization of collections of objects -//! that implement the `Versionize` trait. +//! that implement the `serde` `Serialize`, `Deserialize` trait. Currently, we use +//! [`bincode`](https://docs.rs/bincode/latest/bincode/) for performing the serialization. //! -//! |----------------------------| -//! | 64 bit magic_id | -//! |----------------------------| -//! | SnapshotHdr | -//! |----------------------------| -//! | State | -//! |----------------------------| -//! | optional CRC64 | -//! |----------------------------| +//! The snapshot format uses the following layout: //! -//! Each structure, union or enum is versioned separately and only needs to increment their version -//! if a field is added or removed. For each state snapshot we define 2 versions: -//! - **the format version** which refers to the SnapshotHdr, CRC, or the representation of -//! primitives types (currently we use versionize that uses serde bincode as a backend). The current -//! implementation does not have any logic dependent on it. -//! - **the data version** which refers to the state. +//! |-----------------------------| +//! | 64 bit magic_id | +//! |-----------------------------| +//! | version string | +//! |-----------------------------| +//! | State | +//! |-----------------------------| +//! | optional CRC64 | +//! |-----------------------------| +//! +//! +//! The snapshot format uses a version value in the form of `MAJOR.MINOR.PATCH`. The version is +//! provided by the library clients (it is not tied to this crate). +pub mod crc; mod persist; use std::fmt::Debug; use std::io::{Read, Write}; -use versionize::crc::{CRC64Reader, CRC64Writer}; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; +use semver::Version; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use crate::crc::{CRC64Reader, CRC64Writer}; pub use crate::persist::Persist; -const BASE_MAGIC_ID_MASK: u64 = !0xFFFFu64; - #[cfg(target_arch = "x86_64")] -const BASE_MAGIC_ID: u64 = 0x0710_1984_8664_0000u64; +const SNAPSHOT_MAGIC_ID: u64 = 0x0710_1984_8664_0000u64; #[cfg(target_arch = "aarch64")] -const BASE_MAGIC_ID: u64 = 0x0710_1984_AAAA_0000u64; +const SNAPSHOT_MAGIC_ID: u64 = 0x0710_1984_AAAA_0000u64; /// Error definitions for the Snapshot API. #[derive(Debug, thiserror::Error, displaydoc::Display, PartialEq)] @@ -49,106 +49,108 @@ pub enum Error { /// CRC64 validation failed: {0} Crc64(u64), /// Invalid data version: {0} - InvalidDataVersion(u16), - /// Invalid format version: {0} - InvalidFormatVersion(u16), + InvalidFormatVersion(Version), /// Magic value does not match arch: {0} InvalidMagic(u64), /// Snapshot file is smaller than CRC length. InvalidSnapshotSize, /// An IO error occurred: {0} Io(i32), - /// A versioned serialization/deserialization error occurred: {0} - Versionize(versionize::VersionizeError), + /// An error occured with serialization/deserialization: {0} + Serde(String), } -#[derive(Default, Debug, Versionize)] +/// Firecracker snapshot header +#[derive(Debug, Serialize, Deserialize)] struct SnapshotHdr { - /// Snapshot data version (firecracker version). - data_version: u16, -} - -/// The `Snapshot` API manages serialization and deserialization of collections of objects -/// that implement the `Versionize` trait. -#[derive(Debug)] -pub struct Snapshot { - hdr: SnapshotHdr, - version_map: VersionMap, - // Required for serialization. - target_version: u16, + /// magic value + magic: u64, + /// Snapshot data version + version: Version, } -// Parse a magic_id and return the format version. -fn get_format_version(magic_id: u64) -> Result { - let magic_arch = magic_id & BASE_MAGIC_ID_MASK; - if magic_arch == BASE_MAGIC_ID { - return Ok((magic_id & !BASE_MAGIC_ID_MASK) as u16); +impl SnapshotHdr { + fn new(version: Version) -> Self { + Self { + magic: SNAPSHOT_MAGIC_ID, + version, + } } - Err(Error::InvalidMagic(magic_id)) } -fn build_magic_id(format_version: u16) -> u64 { - BASE_MAGIC_ID | u64::from(format_version) +/// Firecracker snapshot type +/// +/// A type used to store and load Firecracker snapshots of a particular version +#[derive(Debug)] +pub struct Snapshot { + // The snapshot version we can handle + version: Version, } impl Snapshot { /// Creates a new instance which can only be used to save a new snapshot. - pub fn new(version_map: VersionMap, target_version: u16) -> Snapshot { - Snapshot { - version_map, - hdr: SnapshotHdr::default(), - target_version, - } + pub fn new(version: Version) -> Snapshot { + Snapshot { version } } /// Fetches snapshot data version. - pub fn get_data_version(mut reader: &mut T, version_map: &VersionMap) -> Result + pub fn get_format_version(reader: &mut T) -> Result where T: Read + Debug, { - let format_version_map = Self::format_version_map(); - let magic_id = - ::deserialize(&mut reader, &format_version_map, 0 /* unused */) - .map_err(Error::Versionize)?; - - let format_version = get_format_version(magic_id)?; - if format_version > format_version_map.latest_version() || format_version == 0 { - return Err(Error::InvalidFormatVersion(format_version)); - } + let hdr: SnapshotHdr = Self::deserialize(reader)?; + Ok(hdr.version) + } - let hdr: SnapshotHdr = - SnapshotHdr::deserialize(&mut reader, &format_version_map, format_version) - .map_err(Error::Versionize)?; - if hdr.data_version > version_map.latest_version() || hdr.data_version == 0 { - return Err(Error::InvalidDataVersion(hdr.data_version)); - } + /// Helper function to deserialize an object from a reader + pub fn deserialize(reader: &mut T) -> Result + where + T: Read, + O: DeserializeOwned + Debug, + { + bincode::deserialize_from(reader).map_err(|err| Error::Serde(err.to_string())) + } - Ok(hdr.data_version) + /// Helper function to serialize an object to a writer + pub fn serialize(writer: &mut T, data: &O) -> Result<(), Error> + where + T: Write, + O: Serialize + Debug, + { + bincode::serialize_into(writer, data).map_err(|err| Error::Serde(err.to_string())) } - /// Attempts to load an existing snapshot without CRC validation. - pub fn unchecked_load( - mut reader: &mut T, - version_map: VersionMap, - ) -> Result<(O, u16), Error> { - let data_version = Self::get_data_version(&mut reader, &version_map)?; - let res = - O::deserialize(&mut reader, &version_map, data_version).map_err(Error::Versionize)?; - Ok((res, data_version)) + /// Attempts to load an existing snapshot without performing CRC or version validation. + /// + /// This will check that the snapshot magic value is correct. + fn unchecked_load(reader: &mut T) -> Result<(O, Version), Error> + where + T: Read + Debug, + O: DeserializeOwned + Debug, + { + let hdr: SnapshotHdr = Self::deserialize(reader)?; + if hdr.magic != SNAPSHOT_MAGIC_ID { + return Err(Error::InvalidMagic(hdr.magic)); + } + + let data: O = Self::deserialize(reader)?; + Ok((data, hdr.version)) } - /// Attempts to load an existing snapshot and validate CRC. - pub fn load( - reader: &mut T, - snapshot_len: usize, - version_map: VersionMap, - ) -> Result<(O, u16), Error> { + /// Load a snapshot from a reader and validate its CRC + pub fn load(reader: &mut T, snapshot_len: usize) -> Result<(O, Version), Error> + where + T: Read + Debug, + O: DeserializeOwned + Debug, + { let mut crc_reader = CRC64Reader::new(reader); - // Extract snapshot data without stored checksum, which is 8 bytes in size + // Fail-fast if the snapshot length is too small let raw_snapshot_len = snapshot_len .checked_sub(std::mem::size_of::()) .ok_or(Error::InvalidSnapshotSize)?; + + // Read everything apart from the CRC. let mut snapshot = vec![0u8; raw_snapshot_len]; crc_reader .read_exact(&mut snapshot) @@ -158,79 +160,57 @@ impl Snapshot { // 2 statements is important, we first get the checksum computed on the read bytes // then read the stored checksum. let computed_checksum = crc_reader.checksum(); - let format_vm = Self::format_version_map(); - let stored_checksum: u64 = - Versionize::deserialize(&mut crc_reader, &format_vm, 0).map_err(Error::Versionize)?; + let stored_checksum: u64 = Self::deserialize(&mut crc_reader)?; if computed_checksum != stored_checksum { return Err(Error::Crc64(computed_checksum)); } let mut snapshot_slice: &[u8] = snapshot.as_mut_slice(); - Snapshot::unchecked_load::<_, O>(&mut snapshot_slice, version_map) + Snapshot::unchecked_load::<_, O>(&mut snapshot_slice) + } + + /// Load a snapshot from a reader object and perform a snapshot version check + pub fn load_with_version_check( + &self, + reader: &mut T, + snapshot_len: usize, + ) -> Result + where + T: Read + Debug, + O: DeserializeOwned + Debug, + { + let (data, version) = Snapshot::load::<_, O>(reader, snapshot_len)?; + if version.major != self.version.major || version.minor > self.version.minor { + Err(Error::InvalidFormatVersion(version)) + } else { + Ok(data) + } } /// Saves a snapshot and include a CRC64 checksum. - pub fn save(&mut self, writer: &mut T, object: &O) -> Result<(), Error> + pub fn save(&self, writer: &mut T, object: &O) -> Result<(), Error> where T: Write + Debug, - O: Versionize + Debug, + O: Serialize + Debug, { let mut crc_writer = CRC64Writer::new(writer); self.save_without_crc(&mut crc_writer, object)?; + // Now write CRC value let checksum = crc_writer.checksum(); - checksum - .serialize(&mut crc_writer, &Self::format_version_map(), 0) - .map_err(Error::Versionize)?; - Ok(()) + Self::serialize(&mut crc_writer, &checksum) } - // TODO Remove `skip(crc_writer)` when https://github.com/firecracker-microvm/versionize/pull/59 - // is merged and included. /// Save a snapshot with no CRC64 checksum included. - pub fn save_without_crc(&mut self, mut writer: &mut T, object: &O) -> Result<(), Error> + pub fn save_without_crc(&self, mut writer: &mut T, object: &O) -> Result<(), Error> where T: Write, - O: Versionize + Debug, + O: Serialize + Debug, { - self.hdr = SnapshotHdr { - data_version: self.target_version, - }; - - let format_version_map = Self::format_version_map(); - let magic_id = build_magic_id(format_version_map.latest_version()); - - // Serialize magic id using the format version map. - magic_id - .serialize(&mut writer, &format_version_map, 0 /* unused */) - .map_err(Error::Versionize)?; - - // Serialize header using the format version map. - self.hdr - .serialize( - &mut writer, - &format_version_map, - format_version_map.latest_version(), - ) - .map_err(Error::Versionize)?; - - // Serialize the object using the state version map. - object - .serialize(&mut writer, &self.version_map, self.target_version) - .map_err(Error::Versionize)?; - writer - .flush() - .map_err(|ref err| Error::Io(err.raw_os_error().unwrap_or(libc::EINVAL))) - } - - // Returns the current snapshot format version. - // Not to be confused with data version which refers to the aplication - // defined structures. - // This version map allows us to change the underlying storage format - - // for example the way we encode vectors or moving to something else than bincode. - fn format_version_map() -> VersionMap { - // Firecracker snapshot format version 1. - VersionMap::new() + // Write magic value and snapshot version + Self::serialize(&mut writer, &SnapshotHdr::new(self.version.clone()))?; + // Write data + Self::serialize(&mut writer, object) } } @@ -238,352 +218,157 @@ impl Snapshot { mod tests { use super::*; - #[derive(Clone, Debug, Versionize)] - pub struct Test1 { - field_x: u64, - field0: u64, - field1: u32, - } - - #[derive(Clone, Debug, Versionize)] - pub struct Test { - field_x: u64, - field0: u64, - field1: u32, - #[version(start = 2, default_fn = "field2_default")] - field2: u64, - #[version( - start = 3, - default_fn = "field3_default", - ser_fn = "field3_serialize", - de_fn = "field3_deserialize" - )] - field3: String, - #[version( - start = 4, - default_fn = "field4_default", - ser_fn = "field4_serialize", - de_fn = "field4_deserialize" - )] - field4: Vec, - } - - impl Test { - fn field2_default(_: u16) -> u64 { - 20 - } - fn field3_default(_: u16) -> String { - "default".to_owned() - } - fn field4_default(_: u16) -> Vec { - vec![1, 2, 3, 4] - } - fn field4_serialize(&mut self, target_version: u16) -> VersionizeResult<()> { - // Fail if semantic serialization is called for the latest version. - assert_ne!(target_version, Test::version()); - self.field0 = self.field4.iter().sum(); - - if self.field0 == 6666 { - return Err(versionize::VersionizeError::Semantic( - "field4 element sum is 6666".to_owned(), - )); - } - Ok(()) - } - fn field4_deserialize(&mut self, source_version: u16) -> VersionizeResult<()> { - // Fail if semantic deserialization is called for the latest version. - assert_ne!(source_version, Test::version()); - self.field4 = vec![self.field0; 4]; - Ok(()) - } - - fn field3_serialize(&mut self, target_version: u16) -> VersionizeResult<()> { - // Fail if semantic serialization is called for the previous versions only. - assert!(target_version < 3); - self.field_x += 1; - Ok(()) - } - - fn field3_deserialize(&mut self, source_version: u16) -> VersionizeResult<()> { - // Fail if semantic deserialization is called for the latest version. - assert!(source_version < 3); - self.field_x += 1; - if self.field0 == 7777 { - return Err(versionize::VersionizeError::Semantic( - "field0 is 7777".to_owned(), - )); - } - Ok(()) - } - } - #[test] - fn test_get_format_version() { - // Check if `get_format_version()` returns indeed the format - // version (the least significant 2 bytes) if the id is valid - // (the other bytes == BASE_MAGIC_ID). - #[cfg(target_arch = "x86_64")] - let good_magic_id = 0x0710_1984_8664_0001u64; - #[cfg(target_arch = "aarch64")] - let good_magic_id = 0x0710_1984_AAAA_0001u64; - - assert_eq!(get_format_version(good_magic_id).unwrap(), 1u16); - - // Flip a bit to invalidate the arch id. - let invalid_magic_id = good_magic_id | (1u64 << 63); - assert_eq!( - get_format_version(invalid_magic_id).unwrap_err(), - Error::InvalidMagic(invalid_magic_id) - ); - } - - #[test] - fn test_struct_semantic_fn() { - let mut vm = VersionMap::new(); - vm.new_version() - .set_type_version(Test::type_id(), 2) - .new_version() - .set_type_version(Test::type_id(), 3) - .new_version() - .set_type_version(Test::type_id(), 4); - let state = Test { - field0: 0, - field1: 1, - field2: 2, - field3: "test".to_owned(), - field4: vec![4, 3, 2, 1], - field_x: 0, - }; - - let mut snapshot_mem = vec![0u8; 1024]; - - // Serialize as v1. - let mut snapshot = Snapshot::new(vm.clone(), 1); - snapshot - .save_without_crc(&mut snapshot_mem.as_mut_slice(), &state) - .unwrap(); + fn test_parse_version_from_file() { + let snapshot = Snapshot::new(Version::new(1, 0, 42)); - let (mut restored_state, _) = - Snapshot::unchecked_load::<_, Test>(&mut snapshot_mem.as_slice(), vm.clone()).unwrap(); - - // The semantic serializer fn for field4 will set field0 to field4.iter().sum() == 10. - assert_eq!(restored_state.field0, state.field4.iter().sum::()); - // The semantic deserializer for field4 will change field's value to vec![field0; 4]. - assert_eq!(restored_state.field4, vec![restored_state.field0; 4]); - // The semantic serializer and deserializer for field3 will both increment field_x value. - assert_eq!(restored_state.field_x, 2); - // field1 should have the original value. - assert_eq!(restored_state.field1, 1); - // field2 should have the default value as this field was added at version 2. - assert_eq!(restored_state.field2, 20); - - // Serialize as v3. - let mut snapshot = Snapshot::new(vm.clone(), 3); - snapshot - .save_without_crc(&mut snapshot_mem.as_mut_slice(), &state) - .unwrap(); - - (restored_state, _) = - Snapshot::unchecked_load::<_, Test>(&mut snapshot_mem.as_slice(), vm.clone()).unwrap(); - - // We expect only the semantic serializer and deserializer for field4 to be called at - // version 3. The semantic serializer will set field0 to field4.iter().sum() == 10. - assert_eq!(restored_state.field0, state.field4.iter().sum::()); - // The semantic deserializer will create a 4 elements vec with all values == field0. - assert_eq!(restored_state.field4, vec![restored_state.field0; 4]); - // The semantic fn for field3 must not be called at version 3. - assert_eq!(restored_state.field_x, 0); + // Enough memory for the header, 1 byte and the CRC + let mut snapshot_data = vec![0u8; 100]; - // Serialize as v4. - snapshot = Snapshot::new(vm.clone(), 4); snapshot - .save_without_crc(&mut snapshot_mem.as_mut_slice(), &state) + .save(&mut snapshot_data.as_mut_slice(), &42u8) .unwrap(); - (restored_state, _) = - Snapshot::unchecked_load::<_, Test>(&mut snapshot_mem.as_slice(), vm.clone()).unwrap(); - - // The 4 semantic fns must not be called at version 4. - assert_eq!(restored_state.field0, 0); - assert_eq!(restored_state.field4, vec![4, 3, 2, 1]); - - // Test error propagation from `versionize` crate. - // Load operation should fail if we don't use the whole `snapshot_mem` resulted from - // serialization. - snapshot_mem.truncate(10); - let restored_state_result: Result<(Test, _), Error> = - Snapshot::unchecked_load(&mut snapshot_mem.as_slice(), vm); - assert_eq!( - restored_state_result.unwrap_err(), - Error::Versionize(versionize::VersionizeError::Deserialize(String::from( - "Io(Error { kind: UnexpectedEof, message: \"failed to fill whole buffer\" })" - ))) + Snapshot::get_format_version(&mut snapshot_data.as_slice()).unwrap(), + Version::new(1, 0, 42) ); } #[test] - fn test_struct_default_fn() { - let mut vm = VersionMap::new(); - vm.new_version() - .set_type_version(Test::type_id(), 2) - .new_version() - .set_type_version(Test::type_id(), 3) - .new_version() - .set_type_version(Test::type_id(), 4); - let state = Test { - field0: 0, - field1: 1, - field2: 2, - field3: "test".to_owned(), - field4: vec![4, 3, 2, 1], - field_x: 0, - }; - - let state_1 = Test1 { - field_x: 0, - field0: 0, - field1: 1, - }; - - let mut snapshot_mem = vec![0u8; 1024]; - - // Serialize as v1. - let mut snapshot = Snapshot::new(vm.clone(), 1); - snapshot - .save_without_crc(&mut snapshot_mem.as_mut_slice(), &state_1) - .unwrap(); - - let (mut restored_state, _) = - Snapshot::unchecked_load::<_, Test>(&mut snapshot_mem.as_slice(), vm.clone()).unwrap(); - assert_eq!(restored_state.field1, state_1.field1); - assert_eq!(restored_state.field2, 20); - assert_eq!(restored_state.field3, "default"); - - // Serialize as v2. - snapshot = Snapshot::new(vm.clone(), 2); - snapshot - .save_without_crc(&mut snapshot_mem.as_mut_slice(), &state) - .unwrap(); + fn test_bad_snapshot_size() { + let snapshot_data = vec![0u8; 1]; + + let snapshot = Snapshot::new(Version::new(1, 6, 1)); + assert!(matches!( + snapshot.load_with_version_check::<_, u8>( + &mut snapshot_data.as_slice(), + snapshot_data.len() + ), + Err(Error::InvalidSnapshotSize) + )); + } - (restored_state, _) = - Snapshot::unchecked_load::<_, Test>(&mut snapshot_mem.as_slice(), vm.clone()).unwrap(); - assert_eq!(restored_state.field1, state.field1); - assert_eq!(restored_state.field2, 2); - assert_eq!(restored_state.field3, "default"); + #[test] + fn test_bad_reader() { + #[derive(Debug)] + struct BadReader; - // Serialize as v3. - snapshot = Snapshot::new(vm.clone(), 3); - snapshot - .save_without_crc(&mut snapshot_mem.as_mut_slice(), &state) - .unwrap(); + impl Read for BadReader { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + Err(std::io::ErrorKind::InvalidInput.into()) + } + } - (restored_state, _) = - Snapshot::unchecked_load::<_, Test>(&mut snapshot_mem.as_slice(), vm.clone()).unwrap(); - assert_eq!(restored_state.field1, state.field1); - assert_eq!(restored_state.field2, 2); - assert_eq!(restored_state.field3, "test"); + let mut reader = BadReader {}; - // Serialize as v4. - snapshot = Snapshot::new(vm.clone(), 4); - snapshot - .save_without_crc(&mut snapshot_mem.as_mut_slice(), &state) - .unwrap(); - - (restored_state, _) = - Snapshot::unchecked_load::<_, Test>(&mut snapshot_mem.as_slice(), vm.clone()).unwrap(); - assert_eq!(restored_state.field1, state.field1); - assert_eq!(restored_state.field2, 2); - assert_eq!(restored_state.field3, "test"); + let snapshot = Snapshot::new(Version::new(42, 27, 18)); + assert!(matches!( + snapshot.load_with_version_check::<_, u8>(&mut reader, 1024), + Err(Error::Io(_)) + )); } #[test] - fn test_crc_ok() { - let vm = VersionMap::new(); - let state_1 = Test1 { - field_x: 0, - field0: 0, - field1: 1, - }; - - let mut snapshot_mem = vec![0u8; 1024]; - - // Serialize as v1. - let mut snapshot = Snapshot::new(vm.clone(), 1); - snapshot - .save(&mut snapshot_mem.as_mut_slice(), &state_1) - .unwrap(); - - let _ = Snapshot::load::<_, Test1>(&mut snapshot_mem.as_slice(), 38, vm).unwrap(); + fn test_bad_magic() { + let mut data = vec![0u8; 100]; + + let snapshot = Snapshot::new(Version::new(24, 16, 1)); + snapshot.save(&mut data.as_mut_slice(), &42u8).unwrap(); + + // Writing dummy values in the first bytes of the snapshot data (we are on little-endian + // machines) should trigger an `Error::InvalidMagic` error. + data[0] = 0x01; + data[1] = 0x02; + data[2] = 0x03; + data[3] = 0x04; + data[4] = 0x42; + data[5] = 0x43; + data[6] = 0x44; + data[7] = 0x45; + assert!(matches!( + Snapshot::unchecked_load::<_, u8>(&mut data.as_slice()), + Err(Error::InvalidMagic(0x4544_4342_0403_0201u64)) + )); } #[test] - fn test_invalid_snapshot_size() { - let vm = VersionMap::new(); - // Create a snapshot shorter than CRC length. - let snapshot_mem = vec![0u8; 4]; - let expected_err = Error::InvalidSnapshotSize; - let load_result: Result<(Test1, _), Error> = - Snapshot::load(&mut snapshot_mem.as_slice(), 4, vm); - assert_eq!(load_result.unwrap_err(), expected_err); - } + fn test_bad_crc() { + let mut data = vec![0u8; 100]; - #[test] - fn test_corrupted_snapshot() { - let vm = VersionMap::new(); - let state_1 = Test1 { - field_x: 0, - field0: 0, - field1: 1, - }; - - let mut snapshot_mem = vec![0u8; 1024]; - - // Serialize as v1. - let mut snapshot = Snapshot::new(vm.clone(), 1); + let snapshot = Snapshot::new(Version::new(12, 1, 3)); + snapshot.save(&mut data.as_mut_slice(), &42u8).unwrap(); + + // Tamper the bytes written, without touching the previously CRC. snapshot - .save(&mut snapshot_mem.as_mut_slice(), &state_1) + .save_without_crc(&mut data.as_mut_slice(), &43u8) .unwrap(); - snapshot_mem[20] = 123; - #[cfg(target_arch = "aarch64")] - let expected_err = Error::Crc64(0x1960_4E6A_A13F_6615); - #[cfg(target_arch = "x86_64")] - let expected_err = Error::Crc64(0x103F_8F52_8F51_20B1); - - let load_result: Result<(Test1, _), Error> = - Snapshot::load(&mut snapshot_mem.as_slice(), 38, vm); - assert_eq!(load_result.unwrap_err(), expected_err); + assert!(matches!( + snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), + Err(Error::Crc64(_)) + )); } - #[allow(non_upper_case_globals)] - #[allow(non_camel_case_types)] - #[allow(non_snake_case)] #[test] - fn test_kvm_bindings_struct() { - #[repr(C)] - #[derive(Debug, PartialEq, Eq, Versionize)] - pub struct kvm_pit_config { - pub flags: ::std::os::raw::c_uint, - pub pad: [::std::os::raw::c_uint; 15usize], - } - - let state = kvm_pit_config { - flags: 123_456, - pad: [0; 15usize], - }; - - let vm = VersionMap::new(); - let mut snapshot_mem = vec![0u8; 1024]; - // Serialize as v1. - let mut snapshot = Snapshot::new(vm.clone(), 1); + fn test_bad_version() { + let mut data = vec![0u8; 100]; + + // We write a snapshot with version "v1.3.12" + let snapshot = Snapshot::new(Version::new(1, 3, 12)); + snapshot.save(&mut data.as_mut_slice(), &42u8).unwrap(); + + // Different major versions should not work + let snapshot = Snapshot::new(Version::new(2, 3, 12)); + assert!(matches!( + snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), + Err(Error::InvalidFormatVersion(Version { + major: 1, + minor: 3, + patch: 12, + .. + })) + )); + let snapshot = Snapshot::new(Version::new(0, 3, 12)); + assert!(matches!( + snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), + Err(Error::InvalidFormatVersion(Version { + major: 1, + minor: 3, + patch: 12, + .. + })) + )); + + // We can't support minor versions bigger than ours + let snapshot = Snapshot::new(Version::new(1, 2, 12)); + assert!(matches!( + snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), + Err(Error::InvalidFormatVersion(Version { + major: 1, + minor: 3, + patch: 12, + .. + })) + )); + + // But we can support minor versions smaller or equeal to ours. We also support + // all patch versions within our supported major.minor version. + let snapshot = Snapshot::new(Version::new(1, 4, 12)); snapshot - .save_without_crc(&mut snapshot_mem.as_mut_slice(), &state) + .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) + .unwrap(); + let snapshot = Snapshot::new(Version::new(1, 3, 0)); + snapshot + .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) + .unwrap(); + let snapshot = Snapshot::new(Version::new(1, 3, 12)); + snapshot + .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) + .unwrap(); + let snapshot = Snapshot::new(Version::new(1, 3, 1024)); + snapshot + .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) .unwrap(); - - let (restored_state, _) = - Snapshot::unchecked_load::<_, kvm_pit_config>(&mut snapshot_mem.as_slice(), vm) - .unwrap(); - assert_eq!(restored_state, state); } } diff --git a/src/snapshot/tests/test.rs b/src/snapshot/tests/test.rs deleted file mode 100644 index 37d552eed12..00000000000 --- a/src/snapshot/tests/test.rs +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -use snapshot::{Error, Snapshot}; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; - -#[derive(Debug, PartialEq, Eq, Versionize)] -pub enum TestState { - Zero, - One(u32), - #[version(start = 2, default_fn = "default_state_two")] - Two(u64), -} - -impl TestState { - fn default_state_two(&self, target_version: u16) -> VersionizeResult { - match target_version { - 1 => Ok(TestState::One(2)), - i => Err(VersionizeError::Serialize(format!( - "Unknown target version: {}", - i - ))), - } - } -} - -#[derive(Debug, PartialEq, Eq, Versionize)] -pub struct A { - a: u32, - #[version(start = 1, end = 2)] - b: Option, - #[version(start = 2, default_fn = "default_c")] - c: String, -} - -impl A { - fn default_c(_source_version: u16) -> String { - "some_string".to_owned() - } -} - -#[test] -fn test_hardcoded_snapshot_deserialization() { - // We are testing representation compatibility between versions, at the `snapshot` crate - // level, by checking that only the version number and the newly added/removed fields changes - // between versions are reflected in the hardcoded snapshot. - - #[rustfmt::skip] - let v1_hardcoded_snapshot: &[u8] = &[ - // This blob consists of the following: magic_id (8 bytes), - 0x01, 0x00, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "x86_64")] - 0x64, - #[cfg(target_arch = "x86_64")] - 0x86, - 0x84, 0x19, 0x10, 0x07, - // target version (2 bytes) + - 0x01, 0x00, - // `a` field + - 0x10, 0x00, 0x00, 0x00, - // `b` field: Option variant type (1 byte) + inner enum variant type (4 bytes) - // + inner enum value (4 bytes). - 0x01, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - ]; - - #[rustfmt::skip] - let v2_hardcoded_snapshot: &[u8] = &[ - 0x01, - 0x00, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "x86_64")] - 0x64, - #[cfg(target_arch = "x86_64")] - 0x86, 0x84, 0x19, 0x10, 0x07, - // Version 2 + - 0x02, 0x00, - // `a` field + - 0x10, 0x00, 0x00, 0x00, - // `c` field: String len (8 bytes) + actual String; the Option field is not available at v2. - 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x61, 0x6E, 0x64, 0x6F, 0x6D, - ]; - - let mut vm = VersionMap::new(); - vm.new_version() - .set_type_version(A::type_id(), 2) - .set_type_version(TestState::type_id(), 2); - - let mut snapshot_blob = v1_hardcoded_snapshot; - - let (mut restored_struct, _) = - Snapshot::unchecked_load::<_, A>(&mut snapshot_blob, vm.clone()).unwrap(); - - let mut expected_struct = A { - a: 16u32, - b: Some(TestState::One(2)), - c: "some_string".to_owned(), - }; - - assert_eq!(restored_struct, expected_struct); - - snapshot_blob = v2_hardcoded_snapshot; - - (restored_struct, _) = - Snapshot::unchecked_load::<_, A>(&mut snapshot_blob, vm.clone()).unwrap(); - - expected_struct = A { - a: 16u32, - b: None, - c: "random".to_owned(), - }; - - assert_eq!(restored_struct, expected_struct); -} - -#[test] -fn test_invalid_format_version() { - #[rustfmt::skip] - let mut invalid_format_snap: &[u8] = &[ - // This blob consists of the following: magic_id (8 bytes), - 0xAA, 0xAA, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "x86_64")] - 0x64, - #[cfg(target_arch = "x86_64")] - 0x86, - 0x84, 0x19, 0x10, 0x07, - // target version (2 bytes) + - 0x01, 0x00, - // `a` field + - 0x10, 0x00, 0x00, 0x00, - // `b` field: Option variant type (1 byte) + inner enum variant type (4 bytes) - // + inner enum value (4 bytes). - 0x01, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - ]; - - let mut result: Result<(A, _), Error> = - Snapshot::unchecked_load(&mut invalid_format_snap, VersionMap::new()); - let mut expected_err = Error::InvalidFormatVersion(0xAAAA); - assert_eq!(result.unwrap_err(), expected_err); - - #[rustfmt::skip] - let mut null_format_snap: &[u8] = &[ - // This blob consists of the following: magic_id (8 bytes), - 0x00, 0x00, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "x86_64")] - 0x64, - #[cfg(target_arch = "x86_64")] - 0x86, - 0x84, 0x19, 0x10, 0x07, - // target version (2 bytes) + - 0x01, 0x00, - // `a` field + - 0x10, 0x00, 0x00, 0x00, - // `b` field: Option variant type (1 byte) + inner enum variant type (4 bytes) - // + inner enum value (4 bytes). - 0x01, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - ]; - - result = Snapshot::unchecked_load(&mut null_format_snap, VersionMap::new()); - expected_err = Error::InvalidFormatVersion(0); - assert_eq!(result.unwrap_err(), expected_err); -} - -#[test] -fn test_invalid_data_version() { - #[rustfmt::skip] - let mut invalid_data_version_snap: &[u8] = &[ - // This blob consists of the following: magic_id (8 bytes), - 0x01, 0x00, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "x86_64")] - 0x64, - #[cfg(target_arch = "x86_64")] - 0x86, - 0x84, 0x19, 0x10, 0x07, - // target version (2 bytes) + - 0xAA, 0xAA, - // `a` field + - 0x10, 0x00, 0x00, 0x00, - // `b` field: Option variant type (1 byte) + inner enum variant type (4 bytes) - // + inner enum value (4 bytes). - 0x01, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - ]; - let mut result: Result<(A, _), Error> = - Snapshot::unchecked_load(&mut invalid_data_version_snap, VersionMap::new()); - let mut expected_err = Error::InvalidDataVersion(0xAAAA); - assert_eq!(result.unwrap_err(), expected_err); - - #[rustfmt::skip] - let mut null_data_version_snap: &[u8] = &[ - // This blob consists of the following: magic_id (8 bytes), - 0x01, 0x00, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "aarch64")] - 0xAA, - #[cfg(target_arch = "x86_64")] - 0x64, - #[cfg(target_arch = "x86_64")] - 0x86, - 0x84, 0x19, 0x10, 0x07, - // target version (2 bytes) + - 0x00, 0x00, - // `a` field + - 0x10, 0x00, 0x00, 0x00, - // `b` field: Option variant type (1 byte) + inner enum variant type (4 bytes) - // + inner enum value (4 bytes). - 0x01, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - ]; - result = Snapshot::unchecked_load(&mut null_data_version_snap, VersionMap::new()); - expected_err = Error::InvalidDataVersion(0); - assert_eq!(result.unwrap_err(), expected_err); -} diff --git a/src/utils/Cargo.toml b/src/utils/Cargo.toml index ea4f6345a97..04fba2eef90 100644 --- a/src/utils/Cargo.toml +++ b/src/utils/Cargo.toml @@ -14,9 +14,7 @@ libc = "0.2.147" serde = { version = "1.0.165", features = ["derive"] } thiserror = "1.0.32" displaydoc = "0.2.4" -versionize = "0.2.0" -versionize_derive = "0.1.6" -vmm-sys-util = "0.12.0" +vmm-sys-util = "0.12.1" vm-memory = { version = "0.13.0", features = ["backend-mmap", "backend-bitmap"] } log-instrument = { path = "../log-instrument", optional = true } diff --git a/src/utils/src/net/mac.rs b/src/utils/src/net/mac.rs index 4d300b3e405..d0f6e3057c4 100644 --- a/src/utils/src/net/mac.rs +++ b/src/utils/src/net/mac.rs @@ -16,14 +16,12 @@ use std::str::FromStr; use serde::de::{Deserialize, Deserializer, Error}; use serde::ser::{Serialize, Serializer}; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; /// The number of tuples (the ones separated by ":") contained in a MAC address. pub const MAC_ADDR_LEN: u8 = 6; /// Represents a MAC address -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Versionize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] /// Representation of a MAC address. pub struct MacAddr { bytes: [u8; MAC_ADDR_LEN as usize], diff --git a/src/vmm/Cargo.toml b/src/vmm/Cargo.toml index 9368461f378..3884849d674 100644 --- a/src/vmm/Cargo.toml +++ b/src/vmm/Cargo.toml @@ -26,8 +26,6 @@ timerfd = "1.5.0" thiserror = "1.0.32" displaydoc = "0.2.4" userfaultfd = "0.7.0" -versionize = "0.2.0" -versionize_derive = "0.1.6" vhost = { version = "0.10.0", features = ["vhost-user-frontend"] } vm-allocator = "0.1.0" vm-superio = "0.7.0" diff --git a/src/vmm/src/arch/aarch64/gic/gicv2/regs/dist_regs.rs b/src/vmm/src/arch/aarch64/gic/gicv2/regs/dist_regs.rs index 14be488e605..3ec73490038 100644 --- a/src/vmm/src/arch/aarch64/gic/gicv2/regs/dist_regs.rs +++ b/src/vmm/src/arch/aarch64/gic/gicv2/regs/dist_regs.rs @@ -30,7 +30,7 @@ const GICD_SPENDSGIR: DistReg = DistReg::simple(0xF20, 16); // List with relevant distributor registers that we will be restoring. // Order is taken from qemu. // Criteria for the present list of registers: only R/W registers, implementation specific registers -// are not saved. NOTICE: Any changes to this structure require a snapshot version bump. +// are not saved. static VGIC_DIST_REGS: &[DistReg] = &[ GICD_CTLR, GICD_ICENABLER, diff --git a/src/vmm/src/arch/aarch64/gic/gicv2/regs/icc_regs.rs b/src/vmm/src/arch/aarch64/gic/gicv2/regs/icc_regs.rs index e8dbf0ef9e7..aea6cb722b6 100644 --- a/src/vmm/src/arch/aarch64/gic/gicv2/regs/icc_regs.rs +++ b/src/vmm/src/arch/aarch64/gic/gicv2/regs/icc_regs.rs @@ -23,7 +23,6 @@ const GICC_APR2: SimpleReg = SimpleReg::new(0x00D4, 4); const GICC_APR3: SimpleReg = SimpleReg::new(0x00D8, 4); const GICC_APR4: SimpleReg = SimpleReg::new(0x00DC, 4); -// NOTICE: Any changes to this structure require a snapshot version bump. static MAIN_VGIC_ICC_REGS: &[SimpleReg] = &[ GICC_CTLR, GICC_PMR, GICC_BPR, GICC_APBR, GICC_APR1, GICC_APR2, GICC_APR3, GICC_APR4, ]; diff --git a/src/vmm/src/arch/aarch64/gic/gicv3/regs/dist_regs.rs b/src/vmm/src/arch/aarch64/gic/gicv3/regs/dist_regs.rs index 4ae7c337d08..6e0cc8aac23 100644 --- a/src/vmm/src/arch/aarch64/gic/gicv3/regs/dist_regs.rs +++ b/src/vmm/src/arch/aarch64/gic/gicv3/regs/dist_regs.rs @@ -32,8 +32,7 @@ const GICD_IROUTER: DistReg = DistReg::shared_irq(0x6000, 64); // Criteria for the present list of registers: only R/W registers, implementation specific registers // are not saved. GICD_CPENDSGIR and GICD_SPENDSGIR are not saved since these registers are not used // when affinity routing is enabled. Affinity routing GICv3 is enabled by default unless Firecracker -// clears the ICD_CTLR.ARE bit which it does not do. NOTICE: Any changes to this structure require a -// snapshot version bump. +// clears the ICD_CTLR.ARE bit which it does not do. static VGIC_DIST_REGS: &[DistReg] = &[ GICD_CTLR, GICD_STATUSR, diff --git a/src/vmm/src/arch/aarch64/gic/gicv3/regs/icc_regs.rs b/src/vmm/src/arch/aarch64/gic/gicv3/regs/icc_regs.rs index 6ed7df622b1..e455e76ba43 100644 --- a/src/vmm/src/arch/aarch64/gic/gicv3/regs/icc_regs.rs +++ b/src/vmm/src/arch/aarch64/gic/gicv3/regs/icc_regs.rs @@ -29,7 +29,6 @@ const SYS_ICC_AP1R1_EL1: SimpleReg = SimpleReg::sys_icc_ap1rn_el1(1); const SYS_ICC_AP1R2_EL1: SimpleReg = SimpleReg::sys_icc_ap1rn_el1(2); const SYS_ICC_AP1R3_EL1: SimpleReg = SimpleReg::sys_icc_ap1rn_el1(3); -// NOTICE: Any changes to this structure require a snapshot version bump. static MAIN_VGIC_ICC_REGS: &[SimpleReg] = &[ SYS_ICC_SRE_EL1, SYS_ICC_CTLR_EL1, @@ -40,7 +39,6 @@ static MAIN_VGIC_ICC_REGS: &[SimpleReg] = &[ SYS_ICC_BPR1_EL1, ]; -// NOTICE: Any changes to this structure require a snapshot version bump. static AP_VGIC_ICC_REGS: &[SimpleReg] = &[ SYS_ICC_AP0R0_EL1, SYS_ICC_AP0R1_EL1, diff --git a/src/vmm/src/arch/aarch64/gic/gicv3/regs/redist_regs.rs b/src/vmm/src/arch/aarch64/gic/gicv3/regs/redist_regs.rs index 9c966a22d31..1909d9b453b 100644 --- a/src/vmm/src/arch/aarch64/gic/gicv3/regs/redist_regs.rs +++ b/src/vmm/src/arch/aarch64/gic/gicv3/regs/redist_regs.rs @@ -27,7 +27,6 @@ const GICR_IPRIORITYR0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0400, 32) const GICR_ICFGR0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0C00, 8); // List with relevant redistributor registers that we will be restoring. -// NOTICE: Any changes to this structure require a snapshot version bump. static VGIC_RDIST_REGS: &[SimpleReg] = &[ GICR_CTLR, GICR_STATUSR, @@ -37,7 +36,6 @@ static VGIC_RDIST_REGS: &[SimpleReg] = &[ ]; // List with relevant SGI associated redistributor registers that we will be restoring. -// NOTICE: Any changes to this structure require a snapshot version bump. static VGIC_SGI_REGS: &[SimpleReg] = &[ GICR_IGROUPR0, GICR_ICENABLER0, diff --git a/src/vmm/src/arch/aarch64/gic/regs.rs b/src/vmm/src/arch/aarch64/gic/regs.rs index c2e0a303d75..88c4d68c15c 100644 --- a/src/vmm/src/arch/aarch64/gic/regs.rs +++ b/src/vmm/src/arch/aarch64/gic/regs.rs @@ -7,25 +7,24 @@ use std::ops::Range; use kvm_bindings::kvm_device_attr; use kvm_ioctls::DeviceFd; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; +use serde::{Deserialize, Serialize}; use crate::arch::aarch64::gic::GicError; -#[derive(Debug)] -pub struct GicRegState { +#[derive(Debug, Serialize, Deserialize)] +pub struct GicRegState { pub(crate) chunks: Vec, } /// Structure for serializing the state of the Vgic ICC regs -#[derive(Debug, Default, Versionize)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct VgicSysRegsState { pub main_icc_regs: Vec>, pub ap_icc_regs: Vec>>, } /// Structure used for serializing the state of the GIC registers. -#[derive(Debug, Default, Versionize)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct GicState { /// The state of the distributor registers. pub dist: Vec>, @@ -34,39 +33,12 @@ pub struct GicState { } /// Structure used for serializing the state of the GIC registers for a specific vCPU. -#[derive(Debug, Default, Versionize)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct GicVcpuState { pub rdist: Vec>, pub icc: VgicSysRegsState, } -impl Versionize for GicRegState { - fn serialize( - &self, - writer: &mut W, - version_map: &VersionMap, - app_version: u16, - ) -> VersionizeResult<()> { - let chunks = &self.chunks; - assert_eq!(std::mem::size_of_val(chunks), std::mem::size_of::()); - Versionize::serialize(chunks, writer, version_map, app_version) - } - - fn deserialize( - reader: &mut R, - version_map: &VersionMap, - app_version: u16, - ) -> VersionizeResult { - let chunks = Versionize::deserialize(reader, version_map, app_version)?; - assert_eq!(std::mem::size_of_val(&chunks), std::mem::size_of::()); - Ok(Self { chunks }) - } - - fn version() -> u16 { - 1 - } -} - pub(crate) trait MmioReg { fn range(&self) -> Range; @@ -80,7 +52,7 @@ pub(crate) trait MmioReg { pub(crate) trait VgicRegEngine { type Reg: MmioReg; - type RegChunk: Clone + Default + Versionize; + type RegChunk: Clone + Default; fn group() -> u32; diff --git a/src/vmm/src/arch/aarch64/regs.rs b/src/vmm/src/arch/aarch64/regs.rs index 6c0fce0e1f9..0e1dd348897 100644 --- a/src/vmm/src/arch/aarch64/regs.rs +++ b/src/vmm/src/arch/aarch64/regs.rs @@ -6,8 +6,7 @@ // found in the THIRD-PARTY file. use kvm_bindings::*; -use versionize::*; -use versionize_derive::Versionize; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[allow(non_upper_case_globals)] /// PSR (Processor State Register) bits. @@ -73,8 +72,6 @@ macro_rules! arm64_core_reg_id { } pub(crate) use arm64_core_reg_id; -use crate::vstate::memory::ByteValued; - /// This macro computes the ID of a specific ARM64 system register similar to how /// the kernel C macro does. /// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/uapi/asm/kvm.h#L203 @@ -195,33 +192,31 @@ pub fn reg_size(reg_id: u64) -> usize { } /// Storage for aarch64 registers with different sizes. -/// For public usage it is wrapped into `Aarch64RegisterVec` -/// which ensures correctness after deserialization. -#[derive(Default, Debug, Clone, PartialEq, Eq, Versionize)] -struct Aarch64RegisterVecInner { +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct Aarch64RegisterVec { ids: Vec, data: Vec, } -impl Aarch64RegisterVecInner { +impl Aarch64RegisterVec { /// Returns the number of elements in the vector. - fn len(&self) -> usize { + pub fn len(&self) -> usize { self.ids.len() } /// Returns true if the vector contains no elements. - fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.ids.is_empty() } /// Appends a register to the vector, copying register data. - fn push(&mut self, reg: Aarch64RegisterRef<'_>) { + pub fn push(&mut self, reg: Aarch64RegisterRef<'_>) { self.ids.push(reg.id); self.data.extend_from_slice(reg.data); } /// Returns an iterator over stored registers. - fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { Aarch64RegisterVecIterator { index: 0, offset: 0, @@ -231,7 +226,7 @@ impl Aarch64RegisterVecInner { } /// Returns an iterator over stored registers that allows register modifications. - fn iter_mut(&mut self) -> impl Iterator { + pub fn iter_mut(&mut self) -> impl Iterator { Aarch64RegisterVecIteratorMut { index: 0, offset: 0, @@ -241,85 +236,42 @@ impl Aarch64RegisterVecInner { } } -/// Wrapper type around `Aarch64RegisterVecInner`. -/// Needed to ensure correctness of inner state after -/// deserialization. -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct Aarch64RegisterVec { - inner: Aarch64RegisterVecInner, -} - -impl Aarch64RegisterVec { - /// Returns the number of elements in the vector. - pub fn len(&self) -> usize { - self.inner.len() - } - - /// Returns true if the vector contains no elements. - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - /// Appends a register to the vector, copying register data. - pub fn push(&mut self, reg: Aarch64RegisterRef<'_>) { - self.inner.push(reg); - } - - /// Returns an iterator over stored registers. - pub fn iter(&self) -> impl Iterator { - self.inner.iter() - } - - /// Returns an iterator over stored registers that allows register modifications. - pub fn iter_mut(&mut self) -> impl Iterator { - self.inner.iter_mut() +impl Serialize for Aarch64RegisterVec { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Serialize::serialize(&(&self.ids, &self.data), serializer) } } -impl Versionize for Aarch64RegisterVec { - fn serialize( - &self, - writer: &mut W, - version_map: &VersionMap, - target_version: u16, - ) -> VersionizeResult<()> { - self.inner.serialize(writer, version_map, target_version) - } - - fn deserialize( - reader: &mut R, - version_map: &VersionMap, - source_version: u16, - ) -> VersionizeResult +impl<'de> Deserialize<'de> for Aarch64RegisterVec { + fn deserialize(deserializer: D) -> Result where - Self: Sized, + D: Deserializer<'de>, { - let inner = Aarch64RegisterVecInner::deserialize(reader, version_map, source_version)?; + let (ids, data): (Vec, Vec) = Deserialize::deserialize(deserializer)?; + let mut total_size: usize = 0; - for id in inner.ids.iter() { + for id in ids.iter() { let reg_size = reg_size(*id); - if RegSize::U2048_SIZE < reg_size { - return Err(VersionizeError::Deserialize( - "Failed to deserialize aarch64 registers. Registers bigger then 2048 bits are \ - not supported" - .to_string(), + if reg_size > RegSize::U2048_SIZE { + return Err(serde::de::Error::custom( + "Failed to deserialize aarch64 registers. Registers bigger than 2048 bits are \ + not supported", )); } total_size += reg_size; } - if total_size != inner.data.len() { - Err(VersionizeError::Deserialize( - "Failed to deserialize aarch64 registers. Sum of registers sizes is not equal to \ - registers data length" - .to_string(), - )) - } else { - Ok(Self { inner }) + + if total_size != data.len() { + return Err(serde::de::Error::custom( + "Failed to deserialize aarch64 registers. Sum of register sizes is not equal to \ + registers data length", + )); } - } - fn version() -> u16 { - Aarch64RegisterVecInner::version() + Ok(Aarch64RegisterVec { ids, data }) } } @@ -469,61 +421,6 @@ impl<'a> Aarch64RegisterRefMut<'a> { } } -/// Old definition of a struct describing an aarch64 register. -/// This type is only used to have a backward compatibility -/// with old snapshot versions and should not be used anywhere -/// else. -#[derive(Debug, Clone, Copy, Versionize)] -pub struct Aarch64RegisterOld { - /// ID of the register. - pub id: u64, - /// Register data. - pub data: u128, -} - -impl<'a> TryFrom> for Aarch64RegisterOld { - type Error = &'static str; - - fn try_from(value: Aarch64RegisterRef) -> Result { - let reg = match value.size() { - RegSize::U32 => Self { - id: value.id, - data: u128::from(value.value::()), - }, - RegSize::U64 => Self { - id: value.id, - data: u128::from(value.value::()), - }, - RegSize::U128 => Self { - id: value.id, - data: value.value::(), - }, - _ => return Err("Only 32, 64 and 128 bit wide registers are supported"), - }; - Ok(reg) - } -} - -impl<'a> TryFrom<&'a Aarch64RegisterOld> for Aarch64RegisterRef<'a> { - type Error = &'static str; - - fn try_from(value: &'a Aarch64RegisterOld) -> Result { - // # Safety: - // `self.data` is a valid memory and slice size is valid for this type. - let data_ref = value.data.as_slice(); - let reg_size = reg_size(value.id); - if RegSize::U2048_SIZE < reg_size { - return Err("Registers bigger then 2048 bits are not supported"); - } - match RegSize::from(reg_size) { - RegSize::U32 => Ok(Self::new(value.id, &data_ref[..std::mem::size_of::()])), - RegSize::U64 => Ok(Self::new(value.id, &data_ref[..std::mem::size_of::()])), - RegSize::U128 => Ok(Self::new(value.id, data_ref)), - _ => Err("Only 32, 64 and 128 bit wide registers are supported"), - } - } -} - /// Trait for data types that can represent aarch64 /// register data. pub trait Aarch64RegisterData { @@ -581,6 +478,8 @@ reg_data_array!([u8; 256], 256); #[cfg(test)] mod tests { + use snapshot::Snapshot; + use super::*; #[test] @@ -603,13 +502,9 @@ mod tests { v.push(reg2); let mut buf = vec![0; 10000]; - let version_map = VersionMap::new(); - v.serialize(&mut buf.as_mut_slice(), &version_map, 1) - .unwrap(); - let restored = - ::deserialize(&mut buf.as_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut buf.as_mut_slice(), &v).unwrap(); + let restored: Aarch64RegisterVec = Snapshot::deserialize(&mut buf.as_slice()).unwrap(); for (old, new) in v.iter().zip(restored.iter()) { assert_eq!(old, new); @@ -633,15 +528,12 @@ mod tests { v.push(reg2); let mut buf = vec![0; 10000]; - let version_map = VersionMap::new(); - v.serialize(&mut buf.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut buf.as_mut_slice(), &v).unwrap(); // Total size of registers according IDs are 16 + 16 = 32, // but actual data size is 8 + 16 = 24. - ::deserialize(&mut buf.as_slice(), &version_map, 1) - .unwrap_err(); + Snapshot::deserialize::<_, Aarch64RegisterVec>(&mut buf.as_slice()).unwrap_err(); } #[test] @@ -659,19 +551,16 @@ mod tests { v.push(reg); let mut buf = vec![0; 10000]; - let version_map = VersionMap::new(); - v.serialize(&mut buf.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut buf.as_mut_slice(), &v).unwrap(); // 4096 bit wide registers are not supported. - ::deserialize(&mut buf.as_slice(), &version_map, 1) - .unwrap_err(); + Snapshot::deserialize::<_, Aarch64RegisterVec>(&mut buf.as_slice()).unwrap_err(); } #[test] - fn test_aarch64_register_vec_inner() { - let mut v = Aarch64RegisterVecInner::default(); + fn test_aarch64_register_vec() { + let mut v = Aarch64RegisterVec::default(); let reg1_bytes = 1_u8.to_le_bytes(); let reg1 = Aarch64RegisterRef::new(u64::from(KVM_REG_SIZE_U8), ®1_bytes); @@ -823,48 +712,4 @@ mod tests { let reg_ref = Aarch64RegisterRefMut::new(KVM_REG_SIZE_U64, &mut bytes); assert_eq!(reg_ref.value::(), 69); } - - #[test] - fn test_old_reg_to_reg_ref() { - let old_reg = Aarch64RegisterOld { - id: KVM_REG_SIZE_U64, - data: 69, - }; - - let reg_ref: Aarch64RegisterRef = (&old_reg).try_into().unwrap(); - assert_eq!(old_reg.id, reg_ref.id); - assert_eq!(old_reg.data, u128::from(reg_ref.value::())); - - let old_reg = Aarch64RegisterOld { - id: KVM_REG_SIZE_U256, - data: 69, - }; - - let reg_ref: Result = (&old_reg).try_into(); - reg_ref.unwrap_err(); - - // 4096 bit wide reg ID. - let old_reg = Aarch64RegisterOld { - id: 0x0090000000000000, - data: 69, - }; - - let reg_ref: Result = (&old_reg).try_into(); - reg_ref.unwrap_err(); - } - - #[test] - fn test_reg_ref_to_old_reg() { - let reg_bytes = 69_u64.to_le_bytes(); - let reg_ref = Aarch64RegisterRef::new(KVM_REG_SIZE_U64, ®_bytes); - - let reg: Aarch64RegisterOld = reg_ref.try_into().unwrap(); - assert_eq!(reg.id, reg_ref.id); - assert_eq!(reg.data, u128::from(reg_ref.value::())); - - let reg_ref = Aarch64RegisterRef::new(KVM_REG_SIZE_U256, &[0_u8; 32]); - - let reg: Result = reg_ref.try_into(); - reg.unwrap_err(); - } } diff --git a/src/vmm/src/arch/mod.rs b/src/vmm/src/arch/mod.rs index 15c88ead491..e68bf1ec089 100644 --- a/src/vmm/src/arch/mod.rs +++ b/src/vmm/src/arch/mod.rs @@ -3,8 +3,7 @@ use std::fmt; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; +use serde::{Deserialize, Serialize}; /// Module for aarch64 related functionality. #[cfg(target_arch = "aarch64")] @@ -29,7 +28,7 @@ pub use crate::arch::x86_64::{ }; /// Types of devices that can get attached to this platform. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy, Versionize)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)] pub enum DeviceType { /// Device Type: Virtio. Virtio(u32), diff --git a/src/vmm/src/cpu_config/aarch64/static_cpu_templates/mod.rs b/src/vmm/src/cpu_config/aarch64/static_cpu_templates/mod.rs index 5439de556b4..88159e82e2f 100644 --- a/src/vmm/src/cpu_config/aarch64/static_cpu_templates/mod.rs +++ b/src/vmm/src/cpu_config/aarch64/static_cpu_templates/mod.rs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use serde::{Deserialize, Serialize}; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; /// Module with V1N1 CPU template for aarch64 pub mod v1n1; /// Templates available for configuring the supported ARM CPU types. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Versionize)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum StaticCpuTemplate { // Needed for compatibility /// Empty0 diff --git a/src/vmm/src/cpu_config/templates.rs b/src/vmm/src/cpu_config/templates.rs index 599f3386f74..984fd8dcac2 100644 --- a/src/vmm/src/cpu_config/templates.rs +++ b/src/vmm/src/cpu_config/templates.rs @@ -25,8 +25,6 @@ use std::fmt::Debug; pub use common_types::*; use serde::de::Error as SerdeError; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; /// Error for GetCpuTemplate trait. #[derive(Debug, thiserror::Error, displaydoc::Display, PartialEq, Eq)] @@ -106,7 +104,7 @@ impl TryFrom<&str> for CustomCpuTemplate { /// Struct to represent user defined kvm capability. /// Users can add or remove kvm capabilities to be checked /// by FC in addition to those FC checks by default. -#[derive(Debug, Clone, Eq, PartialEq, Versionize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum KvmCapability { /// Add capability to the check list. Add(u32), diff --git a/src/vmm/src/cpu_config/x86_64/static_cpu_templates/mod.rs b/src/vmm/src/cpu_config/x86_64/static_cpu_templates/mod.rs index a267a8b9739..db976ed9658 100644 --- a/src/vmm/src/cpu_config/x86_64/static_cpu_templates/mod.rs +++ b/src/vmm/src/cpu_config/x86_64/static_cpu_templates/mod.rs @@ -3,8 +3,6 @@ use derive_more::Display; use serde::{Deserialize, Serialize}; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; /// Module with C3 CPU template for x86_64 pub mod c3; @@ -19,9 +17,7 @@ pub mod t2s; /// Template types available for configuring the x86 CPU features that map /// to EC2 instances. -#[derive( - Debug, Default, Display, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Versionize, -)] +#[derive(Debug, Default, Display, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum StaticCpuTemplate { /// C3 Template. #[display(fmt = "C3")] diff --git a/src/vmm/src/device_manager/mmio.rs b/src/vmm/src/device_manager/mmio.rs index c775d707b9e..4a31a5d06e1 100644 --- a/src/vmm/src/device_manager/mmio.rs +++ b/src/vmm/src/device_manager/mmio.rs @@ -12,8 +12,7 @@ use std::sync::{Arc, Mutex}; use kvm_ioctls::{IoEventAddress, VmFd}; use linux_loader::cmdline as kernel_cmdline; use log::info; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; +use serde::{Deserialize, Serialize}; use vm_allocator::{AddressAllocator, AllocPolicy, IdAllocator}; #[cfg(target_arch = "aarch64")] @@ -65,8 +64,7 @@ pub enum MmioError { pub const MMIO_LEN: u64 = 0x1000; /// Stores the address range and irq allocated to this device. -#[derive(Clone, Debug, PartialEq, Eq, Versionize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MMIODeviceInfo { /// Mmio address at which the device is registered. pub addr: u64, diff --git a/src/vmm/src/device_manager/persist.rs b/src/vmm/src/device_manager/persist.rs index 96c04cd601c..f8e5ea55cf0 100644 --- a/src/vmm/src/device_manager/persist.rs +++ b/src/vmm/src/device_manager/persist.rs @@ -9,9 +9,8 @@ use std::sync::{Arc, Mutex}; use event_manager::{MutEventSubscriber, SubscriberOps}; use kvm_ioctls::VmFd; use log::{error, warn}; +use serde::{Deserialize, Serialize}; use snapshot::Persist; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; use vm_allocator::AllocPolicy; use super::mmio::*; @@ -79,8 +78,7 @@ pub enum DevicePersistError { } /// Holds the state of a balloon device connected to the MMIO space. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectedBalloonState { /// Device identifier. pub device_id: String, @@ -93,8 +91,7 @@ pub struct ConnectedBalloonState { } /// Holds the state of a virtio block device connected to the MMIO space. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectedVirtioBlockState { /// Device identifier. pub device_id: String, @@ -107,8 +104,7 @@ pub struct ConnectedVirtioBlockState { } /// Holds the state of a vhost-user block device connected to the MMIO space. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectedVhostUserBlockState { /// Device identifier. pub device_id: String, @@ -121,8 +117,7 @@ pub struct ConnectedVhostUserBlockState { } /// Holds the state of a net device connected to the MMIO space. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectedNetState { /// Device identifier. pub device_id: String, @@ -135,8 +130,7 @@ pub struct ConnectedNetState { } /// Holds the state of a vsock device connected to the MMIO space. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectedVsockState { /// Device identifier. pub device_id: String, @@ -149,8 +143,7 @@ pub struct ConnectedVsockState { } /// Holds the state of an entropy device connected to the MMIO space. -// NOTICE: Any chages to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectedEntropyState { /// Device identifier. pub device_id: String, @@ -164,7 +157,7 @@ pub struct ConnectedEntropyState { /// Holds the state of a legacy device connected to the MMIO space. #[cfg(target_arch = "aarch64")] -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectedLegacyState { /// Device identifier. pub type_: DeviceType, @@ -173,8 +166,7 @@ pub struct ConnectedLegacyState { } /// Holds the MMDS data store version. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, PartialEq, Eq, Versionize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum MmdsVersionState { V1, V2, @@ -199,8 +191,7 @@ impl From for MmdsVersionState { } /// Holds the device states. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Default, Clone, Versionize)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct DeviceStates { #[cfg(target_arch = "aarch64")] // State of legacy devices in MMIO space. @@ -208,20 +199,16 @@ pub struct DeviceStates { /// Virtio block device states. pub virtio_block_devices: Vec, /// Vhost-user block device states. - #[version(start = 5, de_fn = "de_vhost_user_block")] pub vhost_user_block_devices: Vec, /// Net device states. pub net_devices: Vec, /// Vsock device state. pub vsock_device: Option, /// Balloon device state. - #[version(start = 2)] pub balloon_device: Option, /// Mmds version. - #[version(start = 3)] pub mmds_version: Option, /// Entropy device state. - #[version(start = 4)] pub entropy_device: Option, } @@ -237,15 +224,6 @@ pub enum SharedDeviceType { Entropy(Arc>), } -impl DeviceStates { - fn de_vhost_user_block(&mut self, source_version: u16) -> VersionizeResult<()> { - if source_version < 5 { - self.vhost_user_block_devices = vec![]; - } - - Ok(()) - } -} pub struct MMIODevManagerConstructorArgs<'a> { pub mem: GuestMemoryMmap, pub vm: &'a VmFd, @@ -668,12 +646,12 @@ impl<'a> Persist<'a> for MMIODeviceManager { #[cfg(test)] mod tests { + use snapshot::Snapshot; use utils::tempfile::TempFile; use super::*; use crate::builder::tests::*; use crate::devices::virtio::block_common::CacheType; - use crate::devices::virtio::net::persist::NetConfigSpaceState; use crate::resources::VmmConfig; use crate::vmm_config::balloon::BalloonDeviceConfig; use crate::vmm_config::entropy::EntropyDeviceConfig; @@ -754,7 +732,6 @@ mod tests { #[test] fn test_device_manager_persistence() { let mut buf = vec![0; 16384]; - let mut version_map = VersionMap::new(); // These need to survive so the restored blocks find them. let _block_files; let mut tmp_sock_file = TempFile::new().unwrap(); @@ -806,56 +783,11 @@ mod tests { uds_path: tmp_sock_file.as_path().to_str().unwrap().to_string(), }; insert_vsock_device(&mut vmm, &mut cmdline, &mut event_manager, vsock_config); - - version_map - .new_version() - .set_type_version(DeviceStates::type_id(), 2); - vmm.mmio_device_manager - .save() - .serialize(&mut buf.as_mut_slice(), &version_map, 2) - .unwrap(); - - version_map - .new_version() - .set_type_version(DeviceStates::type_id(), 3) - .set_type_version(NetConfigSpaceState::type_id(), 2); - - // For snapshot versions that not support persisting the mmds version, it should be - // deserialized as None. The MMIODeviceManager will initialise it as the default if - // there's at least one network device having a MMDS NS. - vmm.mmio_device_manager - .save() - .serialize(&mut buf.as_mut_slice(), &version_map, 2) - .unwrap(); - let device_states: DeviceStates = - DeviceStates::deserialize(&mut buf.as_slice(), &version_map, 2).unwrap(); - assert!(device_states.mmds_version.is_none()); - - vmm.mmio_device_manager - .save() - .serialize(&mut buf.as_mut_slice(), &version_map, 3) - .unwrap(); - // Add an entropy device. let entropy_config = EntropyDeviceConfig::default(); insert_entropy_device(&mut vmm, &mut cmdline, &mut event_manager, entropy_config); - version_map - .new_version() - .set_type_version(DeviceStates::type_id(), 4); - - version_map - .new_version() - .set_type_version(DeviceStates::type_id(), 4) - .set_type_version(NetConfigSpaceState::type_id(), 2); - - vmm.mmio_device_manager - .save() - .serialize(&mut buf.as_mut_slice(), &version_map, 4) - .unwrap(); - let device_states: DeviceStates = - DeviceStates::deserialize(&mut buf.as_slice(), &version_map, 4).unwrap(); - assert!(device_states.entropy_device.is_some()); + Snapshot::serialize(&mut buf.as_mut_slice(), &vmm.mmio_device_manager.save()).unwrap(); // We only want to keep the device map from the original MmioDeviceManager. vmm.mmio_device_manager.soft_clone() @@ -864,8 +796,7 @@ mod tests { let mut event_manager = EventManager::new().expect("Unable to create EventManager"); let vmm = default_vmm(); - let device_states: DeviceStates = - DeviceStates::deserialize(&mut buf.as_slice(), &version_map, 4).unwrap(); + let device_states: DeviceStates = Snapshot::deserialize(&mut buf.as_slice()).unwrap(); let vm_resources = &mut VmResources::default(); let restore_args = MMIODevManagerConstructorArgs { mem: vmm.guest_memory().clone(), @@ -900,7 +831,8 @@ mod tests { ], "boot-source": {{ "kernel_image_path": "", - "initrd_path": null + "initrd_path": null, + "boot_args": null }}, "cpu-config": null, "logger": null, diff --git a/src/vmm/src/devices/virtio/balloon/persist.rs b/src/vmm/src/devices/virtio/balloon/persist.rs index cd201fe6589..5efb366903d 100644 --- a/src/vmm/src/devices/virtio/balloon/persist.rs +++ b/src/vmm/src/devices/virtio/balloon/persist.rs @@ -7,10 +7,9 @@ use std::sync::atomic::AtomicU32; use std::sync::Arc; use std::time::Duration; +use serde::{Deserialize, Serialize}; use snapshot::Persist; use timerfd::{SetTimeFlags, TimerState}; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; use super::*; use crate::devices::virtio::balloon::device::{BalloonStats, ConfigSpace}; @@ -22,8 +21,7 @@ use crate::vstate::memory::GuestMemoryMmap; /// Information about the balloon config's that are saved /// at snapshot. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct BalloonConfigSpaceState { num_pages: u32, actual_pages: u32, @@ -31,8 +29,7 @@ pub struct BalloonConfigSpaceState { /// Information about the balloon stats that are saved /// at snapshot. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct BalloonStatsState { swap_in: Option, swap_out: Option, @@ -84,8 +81,7 @@ impl BalloonStatsState { /// Information about the balloon that are saved /// at snapshot. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct BalloonState { stats_polling_interval_s: u16, stats_desc_index: Option, @@ -178,6 +174,8 @@ impl Persist<'_> for Balloon { mod tests { use std::sync::atomic::Ordering; + use snapshot::Snapshot; + use super::*; use crate::devices::virtio::device::VirtioDevice; use crate::devices::virtio::test_utils::default_mem; @@ -187,19 +185,16 @@ mod tests { fn test_persistence() { let guest_mem = default_mem(); let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); // Create and save the balloon device. let balloon = Balloon::new(0x42, false, 2, false).unwrap(); - ::save(&balloon) - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &balloon.save()).unwrap(); // Deserialize and restore the balloon device. let restored_balloon = Balloon::restore( BalloonConstructorArgs { mem: guest_mem }, - &BalloonState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(), + &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), ) .unwrap(); diff --git a/src/vmm/src/devices/virtio/block_common.rs b/src/vmm/src/devices/virtio/block_common.rs index a4b28113647..0b8488ca8e5 100644 --- a/src/vmm/src/devices/virtio/block_common.rs +++ b/src/vmm/src/devices/virtio/block_common.rs @@ -2,12 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use serde::{Deserialize, Serialize}; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; /// Configuration options for disk caching. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize, Versionize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] pub enum CacheType { /// Flushing mechanic not will be advertised to the guest driver #[default] diff --git a/src/vmm/src/devices/virtio/net/persist.rs b/src/vmm/src/devices/virtio/net/persist.rs index 9f5c4396ba6..0926a5a0d42 100644 --- a/src/vmm/src/devices/virtio/net/persist.rs +++ b/src/vmm/src/devices/virtio/net/persist.rs @@ -7,11 +7,9 @@ use std::io; use std::sync::atomic::AtomicU32; use std::sync::{Arc, Mutex}; -use log::warn; +use serde::{Deserialize, Serialize}; use snapshot::Persist; -use utils::net::mac::{MacAddr, MAC_ADDR_LEN}; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; +use utils::net::mac::MacAddr; use super::device::Net; use super::NET_NUM_QUEUES; @@ -28,35 +26,14 @@ use crate::vstate::memory::GuestMemoryMmap; /// Information about the network config's that are saved /// at snapshot. -#[derive(Debug, Default, Clone, Versionize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct NetConfigSpaceState { - #[version(end = 2, default_fn = "def_guest_mac_old")] - guest_mac: [u8; MAC_ADDR_LEN as usize], - #[version(start = 2, de_fn = "de_guest_mac_v2")] - guest_mac_v2: Option, -} - -impl NetConfigSpaceState { - fn de_guest_mac_v2(&mut self, version: u16) -> VersionizeResult<()> { - // v1.1 and older versions do not have optional MAC address. - warn!("Optional MAC address will be set to older version."); - if version < 2 { - self.guest_mac_v2 = Some(self.guest_mac.into()); - } - Ok(()) - } - - fn def_guest_mac_old(_: u16) -> [u8; MAC_ADDR_LEN as usize] { - // v1.2 and newer don't use this field anyway - Default::default() - } + guest_mac: Option, } /// Information about the network device that are saved /// at snapshot. -#[derive(Debug, Clone, Versionize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct NetState { id: String, tap_if_name: String, @@ -103,8 +80,7 @@ impl Persist<'_> for Net { tx_rate_limiter_state: self.tx_rate_limiter.save(), mmds_ns: self.mmds_ns.as_ref().map(|mmds| mmds.save()), config_space: NetConfigSpaceState { - guest_mac_v2: self.guest_mac, - guest_mac: Default::default(), + guest_mac: self.guest_mac, }, virtio_state: VirtioDeviceState::from_device(self), } @@ -120,7 +96,7 @@ impl Persist<'_> for Net { let mut net = Net::new( state.id.clone(), &state.tap_if_name, - state.config_space.guest_mac_v2, + state.config_space.guest_mac, rx_rate_limiter, tx_rate_limiter, )?; @@ -164,6 +140,8 @@ impl Persist<'_> for Net { mod tests { use std::sync::atomic::Ordering; + use snapshot::Snapshot; + use super::*; use crate::devices::virtio::device::VirtioDevice; use crate::devices::virtio::net::test_utils::{default_net, default_net_no_mmds}; @@ -172,7 +150,6 @@ mod tests { fn validate_save_and_restore(net: Net, mmds_ds: Option>>) { let guest_mem = default_mem(); let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); let id; let tap_if_name; @@ -182,9 +159,7 @@ mod tests { // Create and save the net device. { - ::save(&net) - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &net.save()).unwrap(); // Save some fields that we want to check later. id = net.id.clone(); @@ -204,7 +179,7 @@ mod tests { mem: guest_mem, mmds: mmds_ds, }, - &NetState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(), + &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), ) { Ok(restored_net) => { // Test that virtio specific fields are the same. diff --git a/src/vmm/src/devices/virtio/persist.rs b/src/vmm/src/devices/virtio/persist.rs index 91313f65caf..4293fac01e2 100644 --- a/src/vmm/src/devices/virtio/persist.rs +++ b/src/vmm/src/devices/virtio/persist.rs @@ -7,9 +7,8 @@ use std::num::Wrapping; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; +use serde::{Deserialize, Serialize}; use snapshot::Persist; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; use crate::devices::virtio::device::VirtioDevice; use crate::devices::virtio::gen::virtio_ring::VIRTIO_RING_F_EVENT_IDX; @@ -25,8 +24,7 @@ pub enum PersistError { } /// Queue information saved in snapshot. -#[derive(Clone, Debug, PartialEq, Eq, Versionize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct QueueState { /// The maximal size in elements offered by the device max_size: u16, @@ -50,7 +48,6 @@ pub struct QueueState { next_used: Wrapping, /// The number of added used buffers since last guest kick - #[version(start = 2)] num_added: Wrapping, } @@ -90,8 +87,7 @@ impl Persist<'_> for Queue { } /// State of a VirtioDevice. -#[derive(Clone, Debug, Default, PartialEq, Eq, Versionize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct VirtioDeviceState { /// Device type. pub device_type: u32, @@ -102,11 +98,7 @@ pub struct VirtioDeviceState { /// List of queues. pub queues: Vec, /// The MMIO interrupt status. - #[version(start = 2, de_fn = "de_interrupt_status")] pub interrupt_status: u32, - /// The MMIO interrupt status as a usize. - #[version(end = 2)] - pub interrupt_status_old: usize, /// Flag for activated status. pub activated: bool, } @@ -120,7 +112,6 @@ impl VirtioDeviceState { acked_features: device.acked_features(), queues: device.queues().iter().map(Persist::save).collect(), interrupt_status: device.interrupt_status().load(Ordering::Relaxed), - interrupt_status_old: device.interrupt_status().load(Ordering::Relaxed) as usize, activated: device.is_activated(), } } @@ -174,20 +165,10 @@ impl VirtioDeviceState { } Ok(queues) } - - #[allow(clippy::cast_possible_truncation)] - fn de_interrupt_status(&mut self, version: u16) -> VersionizeResult<()> { - // v1 uses a usize type for interrupt status. - if version < 2 { - self.interrupt_status = self.interrupt_status_old as u32; - } - Ok(()) - } } /// Transport information saved in snapshot. -#[derive(Clone, Debug, PartialEq, Eq, Versionize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MmioTransportState { // The register where feature bits are stored. features_select: u32, @@ -244,6 +225,7 @@ impl Persist<'_> for MmioTransport { #[cfg(test)] mod tests { + use snapshot::Snapshot; use utils::tempfile::TempFile; use super::*; @@ -337,35 +319,24 @@ mod tests { let queue = Queue::new(128); let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); - queue - .save() - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &queue.save()).unwrap(); - let restored_queue = Queue::restore( - (), - &QueueState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(), - ) - .unwrap(); + let restored_queue = + Queue::restore((), &Snapshot::deserialize(&mut mem.as_slice()).unwrap()).unwrap(); assert_eq!(restored_queue, queue); } #[test] - fn test_virtio_device_state_versionize() { + fn test_virtio_device_state_serde() { let dummy = DummyDevice::new(); let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); let state = VirtioDeviceState::from_device(&dummy); - state - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &state).unwrap(); - let restored_state = - VirtioDeviceState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(); + let restored_state: VirtioDeviceState = Snapshot::deserialize(&mut mem.as_slice()).unwrap(); assert_eq!(restored_state, state); } @@ -390,12 +361,8 @@ mod tests { device: Arc>, ) { let mut buf = vec![0; 4096]; - let version_map = VersionMap::new(); - mmio_transport - .save() - .serialize(&mut buf.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut buf.as_mut_slice(), &mmio_transport.save()).unwrap(); let restore_args = MmioTransportConstructorArgs { mem, @@ -404,7 +371,7 @@ mod tests { }; let restored_mmio_transport = MmioTransport::restore( restore_args, - &MmioTransportState::deserialize(&mut buf.as_slice(), &version_map, 1).unwrap(), + &Snapshot::deserialize(&mut buf.as_slice()).unwrap(), ) .unwrap(); diff --git a/src/vmm/src/devices/virtio/rng/persist.rs b/src/vmm/src/devices/virtio/rng/persist.rs index 1d2b139b873..c6de8a1d4d8 100644 --- a/src/vmm/src/devices/virtio/rng/persist.rs +++ b/src/vmm/src/devices/virtio/rng/persist.rs @@ -3,9 +3,8 @@ //! Defines the structures needed for saving/restoring entropy devices. +use serde::{Deserialize, Serialize}; use snapshot::Persist; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; use crate::devices::virtio::persist::{PersistError as VirtioStateError, VirtioDeviceState}; use crate::devices::virtio::queue::FIRECRACKER_MAX_QUEUE_SIZE; @@ -15,7 +14,7 @@ use crate::rate_limiter::persist::RateLimiterState; use crate::rate_limiter::RateLimiter; use crate::vstate::memory::GuestMemoryMmap; -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct EntropyState { virtio_state: VirtioDeviceState, rate_limiter_state: RateLimiterState, @@ -80,6 +79,8 @@ impl Persist<'_> for Entropy { mod tests { use std::sync::atomic::Ordering; + use snapshot::Snapshot; + use super::*; use crate::devices::virtio::device::VirtioDevice; use crate::devices::virtio::rng::device::ENTROPY_DEV_ID; @@ -90,15 +91,12 @@ mod tests { let mut mem = vec![0u8; 4096]; let entropy = Entropy::new(RateLimiter::default()).unwrap(); - let version_map = VersionMap::new(); - ::save(&entropy) - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &entropy.save()).unwrap(); let guest_mem = create_virtio_mem(); let restored = Entropy::restore( EntropyConstructorArgs(guest_mem), - &EntropyState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(), + &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), ) .unwrap(); diff --git a/src/vmm/src/devices/virtio/vhost_user_block/persist.rs b/src/vmm/src/devices/virtio/vhost_user_block/persist.rs index d62908531b3..cdf3eef5d00 100644 --- a/src/vmm/src/devices/virtio/vhost_user_block/persist.rs +++ b/src/vmm/src/devices/virtio/vhost_user_block/persist.rs @@ -3,9 +3,8 @@ //! Defines the structures needed for saving/restoring block devices. +use serde::{Deserialize, Serialize}; use snapshot::Persist; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; use super::device::VhostUserBlock; use super::VhostUserBlockError; @@ -14,8 +13,7 @@ use crate::devices::virtio::persist::VirtioDeviceState; use crate::vstate::memory::GuestMemoryMmap; /// vhost-user block device state. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct VhostUserBlockState { id: String, partuuid: Option, diff --git a/src/vmm/src/devices/virtio/virtio_block/persist.rs b/src/vmm/src/devices/virtio/virtio_block/persist.rs index 5e4c65aad4d..ae274175884 100644 --- a/src/vmm/src/devices/virtio/virtio_block/persist.rs +++ b/src/vmm/src/devices/virtio/virtio_block/persist.rs @@ -6,10 +6,9 @@ use std::sync::atomic::AtomicU32; use std::sync::Arc; +use serde::{Deserialize, Serialize}; use snapshot::Persist; use utils::eventfd::EventFd; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; use super::device::DiskProperties; use super::*; @@ -25,8 +24,7 @@ use crate::rate_limiter::RateLimiter; use crate::vstate::memory::GuestMemoryMmap; /// Holds info about block's file engine type. Gets saved in snapshot. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Versionize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum FileEngineTypeState { /// Sync File Engine. // If the snap version does not contain the `FileEngineType`, it must have been snapshotted @@ -56,27 +54,18 @@ impl From for FileEngineType { } /// Holds info about the block device. Gets saved in snapshot. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct VirtioBlockState { id: String, partuuid: Option, - #[version(start = 2, default_fn = "default_cache_type_flush")] cache_type: CacheType, root_device: bool, disk_path: String, virtio_state: VirtioDeviceState, rate_limiter_state: RateLimiterState, - #[version(start = 3)] file_engine_type: FileEngineTypeState, } -impl VirtioBlockState { - fn default_cache_type_flush(_source_version: u16) -> CacheType { - CacheType::Unsafe - } -} - /// Auxiliary structure for creating a device when resuming from a snapshot. #[derive(Debug)] pub struct VirtioBlockConstructorArgs { @@ -184,6 +173,7 @@ impl Persist<'_> for VirtioBlock { mod tests { use std::sync::atomic::Ordering; + use snapshot::Snapshot; use utils::tempfile::TempFile; use super::*; @@ -212,15 +202,8 @@ mod tests { // Save the block device. let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); - ::save(&block) - .serialize(&mut mem.as_mut_slice(), &version_map, 2) - .unwrap(); - - ::save(&block) - .serialize(&mut mem.as_mut_slice(), &version_map, 3) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &block.save()).unwrap(); } #[test] @@ -241,10 +224,6 @@ mod tests { let f = TempFile::new().unwrap(); f.as_file().set_len(0x1000).unwrap(); - let mut version_map = VersionMap::new(); - version_map - .new_version() - .set_type_version(VirtioBlockState::type_id(), 3); if !FileEngineType::Async.is_supported().unwrap() { // Test what happens when restoring an Async engine on a kernel that does not support @@ -272,14 +251,12 @@ mod tests { // Overwrite the engine type state with Async. block_state.file_engine_type = FileEngineTypeState::Async; - block_state - .serialize(&mut mem.as_mut_slice(), &version_map, 2) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &block_state).unwrap(); // Restore the block device. let restored_block = VirtioBlock::restore( VirtioBlockConstructorArgs { mem: default_mem() }, - &VirtioBlockState::deserialize(&mut mem.as_slice(), &version_map, 2).unwrap(), + &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), ) .unwrap(); @@ -311,16 +288,13 @@ mod tests { // Save the block device. let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); - ::save(&block) - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &block.save()).unwrap(); // Restore the block device. let restored_block = VirtioBlock::restore( VirtioBlockConstructorArgs { mem: guest_mem }, - &VirtioBlockState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(), + &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), ) .unwrap(); diff --git a/src/vmm/src/devices/virtio/vsock/persist.rs b/src/vmm/src/devices/virtio/vsock/persist.rs index 8c6784c6f50..8930ee781ae 100644 --- a/src/vmm/src/devices/virtio/vsock/persist.rs +++ b/src/vmm/src/devices/virtio/vsock/persist.rs @@ -7,9 +7,8 @@ use std::fmt::Debug; use std::sync::atomic::AtomicU32; use std::sync::Arc; +use serde::{Deserialize, Serialize}; use snapshot::Persist; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; use super::*; use crate::devices::virtio::device::DeviceState; @@ -19,8 +18,7 @@ use crate::devices::virtio::vsock::TYPE_VSOCK; use crate::vstate::memory::GuestMemoryMmap; /// The Vsock serializable state. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct VsockState { /// The vsock backend state. pub backend: VsockBackendState, @@ -29,8 +27,7 @@ pub struct VsockState { } /// The Vsock frontend serializable state. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct VsockFrontendState { /// Context IDentifier. pub cid: u64, @@ -38,16 +35,14 @@ pub struct VsockFrontendState { } /// An enum for the serializable backend state types. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum VsockBackendState { /// UDS backend state. Uds(VsockUdsState), } /// The Vsock Unix Backend serializable state. -// NOTICE: Any changes to this structure require a snapshot version bump. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct VsockUdsState { /// The path for the UDS socket. pub(crate) path: String, @@ -139,6 +134,7 @@ where #[cfg(test)] pub(crate) mod tests { + use snapshot::Snapshot; use utils::byte_order; use super::device::AVAIL_FEATURES; @@ -181,7 +177,6 @@ pub(crate) mod tests { // Test serialization let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); // Save backend and device state separately. let state = VsockState { @@ -189,11 +184,9 @@ pub(crate) mod tests { frontend: ctx.device.save(), }; - state - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &state).unwrap(); - let restored_state = VsockState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(); + let restored_state: VsockState = Snapshot::deserialize(&mut mem.as_slice()).unwrap(); let mut restored_device = Vsock::restore( VsockConstructorArgs { mem: ctx.mem.clone(), diff --git a/src/vmm/src/lib.rs b/src/vmm/src/lib.rs index 7126a59b48d..06a1e50f422 100644 --- a/src/vmm/src/lib.rs +++ b/src/vmm/src/lib.rs @@ -99,8 +99,6 @@ pub mod seccomp_filters; pub mod signal_handler; /// Utility functions for integration and benchmark testing pub mod utilities; -/// microVM state versions. -pub mod version_map; /// Wrappers over structures used to configure the VMM. pub mod vmm_config; /// Module with virtual state structs. diff --git a/src/vmm/src/mmds/persist.rs b/src/vmm/src/mmds/persist.rs index 2f4a6157a61..b1282b9c2e0 100644 --- a/src/vmm/src/mmds/persist.rs +++ b/src/vmm/src/mmds/persist.rs @@ -6,17 +6,15 @@ use std::net::Ipv4Addr; use std::sync::{Arc, Mutex}; +use serde::{Deserialize, Serialize}; use snapshot::Persist; use utils::net::mac::{MacAddr, MAC_ADDR_LEN}; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; use super::ns::MmdsNetworkStack; use crate::mmds::data_store::Mmds; -// NOTICE: Any changes to this structure require a snapshot version bump. /// State of a MmdsNetworkStack. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct MmdsNetworkStackState { mac_addr: [u8; MAC_ADDR_LEN as usize], ipv4_addr: u32, @@ -60,6 +58,8 @@ impl Persist<'_> for MmdsNetworkStack { #[cfg(test)] mod tests { + use snapshot::Snapshot; + use super::*; #[test] @@ -67,15 +67,12 @@ mod tests { let ns = MmdsNetworkStack::new_with_defaults(None, Arc::new(Mutex::new(Mmds::default()))); let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); - ns.save() - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &ns.save()).unwrap(); let restored_ns = MmdsNetworkStack::restore( Arc::new(Mutex::new(Mmds::default())), - &MmdsNetworkStackState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(), + &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), ) .unwrap(); diff --git a/src/vmm/src/persist.rs b/src/vmm/src/persist.rs index 864041c7d96..ef2769d0e0e 100644 --- a/src/vmm/src/persist.rs +++ b/src/vmm/src/persist.rs @@ -12,13 +12,12 @@ use std::path::Path; use std::sync::{Arc, Mutex}; use seccompiler::BpfThreadMap; -use serde::Serialize; +use semver::Version; +use serde::{Deserialize, Serialize}; use snapshot::Snapshot; use userfaultfd::{FeatureFlags, Uffd, UffdBuilder}; use utils::sock_ctrl_msg::ScmSocket; use utils::u64_to_usize; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::vcpu::{get_manufacturer_id_from_host, get_manufacturer_id_from_state}; @@ -44,43 +43,19 @@ use crate::vstate::vcpu::{VcpuSendEventError, VcpuState}; use crate::vstate::vm::VmState; use crate::{mem_size_mib, vstate, EventManager, Vmm, VmmError}; -#[cfg(target_arch = "x86_64")] -const FC_V0_23_MAX_DEVICES: u32 = 11; - /// Holds information related to the VM that is not part of VmState. -#[derive(Clone, Debug, Default, PartialEq, Eq, Versionize, Serialize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] pub struct VmInfo { /// Guest memory size. pub mem_size_mib: u64, /// smt information - #[version(start = 2, default_fn = "def_smt")] pub smt: bool, /// CPU template type - #[version(start = 2, default_fn = "def_cpu_template")] pub cpu_template: StaticCpuTemplate, /// Boot source information. - #[version(start = 2, default_fn = "def_boot_source")] pub boot_source: BootSourceConfig, } -impl VmInfo { - fn def_smt(_: u16) -> bool { - warn!("SMT field not found in snapshot."); - false - } - - fn def_cpu_template(_: u16) -> StaticCpuTemplate { - warn!("CPU template field not found in snapshot."); - StaticCpuTemplate::default() - } - - fn def_boot_source(_: u16) -> BootSourceConfig { - warn!("Boot source information not found in snapshot."); - BootSourceConfig::default() - } -} - impl From<&VmResources> for VmInfo { fn from(value: &VmResources) -> Self { Self { @@ -93,8 +68,7 @@ impl From<&VmResources> for VmInfo { } /// Contains the necesary state for saving/restoring a microVM. -#[derive(Debug, Default, Versionize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Debug, Default, Serialize, Deserialize)] pub struct MicrovmState { /// Miscellaneous VM info. pub vm_info: VmInfo, @@ -158,10 +132,7 @@ pub enum MicrovmStateError { pub enum CreateSnapshotError { /// Cannot get dirty bitmap: {0} DirtyBitmap(VmmError), - /// The virtio devices use a features that is incompatible with older versions of Firecracker: {0} - IncompatibleVirtioFeature(&'static str), - /// Invalid microVM version format - InvalidVersionFormat, + #[rustfmt::skip] /// Cannot translate microVM version to snapshot data version UnsupportedVersion, /// Cannot write memory file: {0} @@ -176,30 +147,22 @@ pub enum CreateSnapshotError { SnapshotBackingFile(&'static str, io::Error), /// Size mismatch when writing diff snapshot on top of base layer: base layer size is {0} but diff layer is size {1}. SnapshotBackingFileLengthMismatch(u64, u64), - #[cfg(target_arch = "x86_64")] - /// Too many devices attached: {0}. The maximum number allowed for the snapshot data version requested is {FC_V0_23_MAX_DEVICES:}. - TooManyDevices(usize), } +/// Snapshot version +pub const SNAPSHOT_VERSION: Version = Version::new(1, 0, 0); + /// Creates a Microvm snapshot. pub fn create_snapshot( vmm: &mut Vmm, vm_info: &VmInfo, params: &CreateSnapshotParams, - version_map: VersionMap, ) -> Result<(), CreateSnapshotError> { - let snapshot_data_version = version_map.latest_version(); - let microvm_state = vmm .save_state(vm_info) .map_err(CreateSnapshotError::MicrovmState)?; - snapshot_state_to_file( - µvm_state, - ¶ms.snapshot_path, - snapshot_data_version, - version_map, - )?; + snapshot_state_to_file(µvm_state, ¶ms.snapshot_path)?; snapshot_memory_to_file(vmm, ¶ms.mem_file_path, params.snapshot_type)?; @@ -209,8 +172,6 @@ pub fn create_snapshot( fn snapshot_state_to_file( microvm_state: &MicrovmState, snapshot_path: &Path, - snapshot_data_version: u16, - version_map: VersionMap, ) -> Result<(), CreateSnapshotError> { use self::CreateSnapshotError::*; let mut snapshot_file = OpenOptions::new() @@ -220,7 +181,7 @@ fn snapshot_state_to_file( .open(snapshot_path) .map_err(|err| SnapshotBackingFile("open", err))?; - let mut snapshot = Snapshot::new(version_map, snapshot_data_version); + let snapshot = Snapshot::new(SNAPSHOT_VERSION); snapshot .save(&mut snapshot_file, microvm_state) .map_err(SerializeMicrovmState)?; @@ -428,10 +389,9 @@ pub fn restore_from_snapshot( event_manager: &mut EventManager, seccomp_filters: &BpfThreadMap, params: &LoadSnapshotParams, - version_map: VersionMap, vm_resources: &mut VmResources, ) -> Result>, RestoreFromSnapshotError> { - let microvm_state = snapshot_state_from_file(¶ms.snapshot_path, version_map)?; + let microvm_state = snapshot_state_from_file(¶ms.snapshot_path)?; // Some sanity checks before building the microvm. snapshot_state_sanity_check(µvm_state)?; @@ -482,13 +442,14 @@ pub enum SnapshotStateFromFileError { fn snapshot_state_from_file( snapshot_path: &Path, - version_map: VersionMap, ) -> Result { + let snapshot = Snapshot::new(SNAPSHOT_VERSION); let mut snapshot_reader = File::open(snapshot_path).map_err(SnapshotStateFromFileError::Open)?; let metadata = std::fs::metadata(snapshot_path).map_err(SnapshotStateFromFileError::Meta)?; let snapshot_len = u64_to_usize(metadata.len()); - let (state, _) = Snapshot::load(&mut snapshot_reader, snapshot_len, version_map) + let state: MicrovmState = snapshot + .load_with_version_check(&mut snapshot_reader, snapshot_len) .map_err(SnapshotStateFromFileError::Load)?; Ok(state) } @@ -675,7 +636,7 @@ mod tests { } #[test] - fn test_microvmstate_versionize() { + fn test_microvm_state_snapshot() { let vmm = default_vmm_with_devices(); let states = vmm.mmio_device_manager.save(); @@ -705,17 +666,10 @@ mod tests { }; let mut buf = vec![0; 10000]; - let mut version_map = VersionMap::new(); - - version_map - .new_version() - .set_type_version(DeviceStates::type_id(), 2); - microvm_state - .serialize(&mut buf.as_mut_slice(), &version_map, 2) - .unwrap(); + Snapshot::serialize(&mut buf.as_mut_slice(), µvm_state).unwrap(); - let restored_microvm_state = - MicrovmState::deserialize(&mut buf.as_slice(), &version_map, 2).unwrap(); + let restored_microvm_state: MicrovmState = + Snapshot::deserialize(&mut buf.as_slice()).unwrap(); assert_eq!(restored_microvm_state.vm_info, microvm_state.vm_info); assert_eq!( diff --git a/src/vmm/src/rate_limiter/persist.rs b/src/vmm/src/rate_limiter/persist.rs index e15775ed6fd..e621433ccf3 100644 --- a/src/vmm/src/rate_limiter/persist.rs +++ b/src/vmm/src/rate_limiter/persist.rs @@ -3,15 +3,13 @@ //! Defines the structures needed for saving/restoring a RateLimiter. +use serde::{Deserialize, Serialize}; use snapshot::Persist; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; use super::*; -// NOTICE: Any changes to this structure require a snapshot version bump. /// State for saving a TokenBucket. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TokenBucketState { size: u64, one_time_burst: u64, @@ -53,9 +51,8 @@ impl Persist<'_> for TokenBucket { } } -// NOTICE: Any changes to this structure require a snapshot version bump. /// State for saving a RateLimiter. -#[derive(Debug, Clone, Versionize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct RateLimiterState { ops: Option, bandwidth: Option, @@ -95,6 +92,8 @@ impl Persist<'_> for RateLimiter { #[cfg(test)] mod tests { + use snapshot::Snapshot; + use super::*; #[test] @@ -117,16 +116,10 @@ mod tests { // Test serialization. let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); - tb.save() - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); - - let restored_tb = TokenBucket::restore( - (), - &TokenBucketState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(), - ) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &tb.save()).unwrap(); + + let restored_tb = + TokenBucket::restore((), &Snapshot::deserialize(&mut mem.as_slice()).unwrap()).unwrap(); assert!(tb.partial_eq(&restored_tb)); } @@ -187,16 +180,9 @@ mod tests { // Test serialization. let mut mem = vec![0; 4096]; - let version_map = VersionMap::new(); - rate_limiter - .save() - .serialize(&mut mem.as_mut_slice(), &version_map, 1) - .unwrap(); - let restored_rate_limiter = RateLimiter::restore( - (), - &RateLimiterState::deserialize(&mut mem.as_slice(), &version_map, 1).unwrap(), - ) - .unwrap(); + Snapshot::serialize(&mut mem.as_mut_slice(), &rate_limiter.save()).unwrap(); + let restored_rate_limiter = + RateLimiter::restore((), &Snapshot::deserialize(&mut mem.as_slice()).unwrap()).unwrap(); assert!(rate_limiter .ops() diff --git a/src/vmm/src/rpc_interface.rs b/src/vmm/src/rpc_interface.rs index ec5d583ef2e..ea3579b17f6 100644 --- a/src/vmm/src/rpc_interface.rs +++ b/src/vmm/src/rpc_interface.rs @@ -24,7 +24,6 @@ use crate::logger::{info, warn, LoggerConfig, *}; use crate::mmds::data_store::{self, Mmds}; use crate::persist::{CreateSnapshotError, RestoreFromSnapshotError, VmInfo}; use crate::resources::VmmConfig; -use crate::version_map::VERSION_MAP; use crate::vmm_config::balloon::{ BalloonConfigError, BalloonDeviceConfig, BalloonStats, BalloonUpdateConfig, BalloonUpdateStatsConfig, @@ -576,7 +575,6 @@ impl<'a> PrebootApiController<'a> { self.event_manager, self.seccomp_filters, load_params, - VERSION_MAP.clone(), self.vm_resources, ) .map_err(|err| { @@ -774,12 +772,7 @@ impl RuntimeApiController { let vm_info = VmInfo::from(&self.vm_resources); let create_start_us = utils::time::get_time_us(utils::time::ClockType::Monotonic); - create_snapshot( - &mut locked_vmm, - &vm_info, - create_params, - VERSION_MAP.clone(), - )?; + create_snapshot(&mut locked_vmm, &vm_info, create_params)?; match create_params.snapshot_type { SnapshotType::Full => { @@ -1234,7 +1227,6 @@ mod tests { _: &mut Vmm, _: &VmInfo, _: &CreateSnapshotParams, - _: versionize::VersionMap, ) -> Result<(), CreateSnapshotError> { Ok(()) } @@ -1246,7 +1238,6 @@ mod tests { _: &mut EventManager, _: &BpfThreadMap, _: &LoadSnapshotParams, - _: versionize::VersionMap, _: &mut MockVmRes, ) -> Result>, RestoreFromSnapshotError> { Ok(Arc::new(Mutex::new(MockVmm::default()))) diff --git a/src/vmm/src/version_map.rs b/src/vmm/src/version_map.rs deleted file mode 100644 index e359e082188..00000000000 --- a/src/vmm/src/version_map.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -//! Provides the VersionMap that deals with the microvm state versions. - -use std::collections::HashMap; - -use lazy_static::lazy_static; -use semver::Version; -use versionize::{VersionMap, Versionize}; - -use crate::device_manager::persist::DeviceStates; -use crate::devices::virtio::net::persist::NetConfigSpaceState; -use crate::devices::virtio::persist::{QueueState, VirtioDeviceState}; -use crate::devices::virtio::virtio_block::persist::VirtioBlockState; -use crate::persist::VmInfo; -use crate::vstate::vcpu::VcpuState; -use crate::vstate::vm::VmState; - -/// Snap version for Firecracker v0.23 -#[cfg(target_arch = "x86_64")] -pub const FC_V0_23_SNAP_VERSION: u16 = 1; -/// Snap version for Firecracker v0.24 -pub const FC_V0_24_SNAP_VERSION: u16 = 2; -/// Snap version for Firecracker v0.25 -pub const FC_V0_25_SNAP_VERSION: u16 = 3; -/// Snap version for Firecracker v1.0 -pub const FC_V1_0_SNAP_VERSION: u16 = 4; -/// Snap version for Firecracker v1.1 -pub const FC_V1_1_SNAP_VERSION: u16 = 5; -/// Snap version for Firecracker v1.2 -pub const FC_V1_2_SNAP_VERSION: u16 = 6; -/// Snap version for Firecracker v1.3 -pub const FC_V1_3_SNAP_VERSION: u16 = 7; -/// Snap version for Firecracker v1.4 -pub const FC_V1_4_SNAP_VERSION: u16 = 8; -/// Snap version for Firecracker v1.5 -pub const FC_V1_5_SNAP_VERSION: u16 = 9; -/// Snap version for Firecracker v1.6 -pub const FC_V1_6_SNAP_VERSION: u16 = 10; -/// Snap version for Firecracker v1.7 -pub const FC_V1_7_SNAP_VERSION: u16 = 11; - -lazy_static! { - // Note: until we have a better design, this needs to be updated when the version changes. - /// Static instance used for handling microVM state versions. - pub static ref VERSION_MAP: VersionMap = { - // v0.23 - all structs and root version are set to 1. - let mut version_map = VersionMap::new(); - - // v0.24 state change mappings. - version_map.new_version().set_type_version(DeviceStates::type_id(), 2); - - // v0.25 state change mappings. - version_map.new_version().set_type_version(VirtioBlockState::type_id(), 2); - #[cfg(target_arch = "x86_64")] - version_map.set_type_version(VcpuState::type_id(), 2); - - // v1.0 state change mappings. - version_map.new_version().set_type_version(QueueState::type_id(), 2); - version_map.set_type_version(VirtioBlockState::type_id(), 3); - - // v1.1 state change mappings. - version_map.new_version().set_type_version(DeviceStates::type_id(), 3); - - // v1.2 state change mappings. - version_map.new_version().set_type_version(VmInfo::type_id(), 2); - version_map.set_type_version(NetConfigSpaceState::type_id(), 2); - #[cfg(target_arch = "x86_64")] - version_map.set_type_version(VcpuState::type_id(), 3); - - // v1.3 - no changes introduced, but we need to bump as mapping - // between firecracker minor versions and snapshot versions needs - // to be 1-to-1 (see below) - version_map.new_version(); - - // v1.4 state change mappings. - version_map.new_version().set_type_version(DeviceStates::type_id(), 4); - - // v1.5 state change mappings. - version_map.new_version(); - #[cfg(target_arch = "aarch64")] - version_map.set_type_version(VcpuState::type_id(), 2); - - version_map.set_type_version(VmState::type_id(), 2); - - // v1.6 state change mappings - version_map.new_version(); - - version_map.set_type_version(VirtioDeviceState::type_id(), 2); - version_map.set_type_version(DeviceStates::type_id(), 5); - - // v1.7 state change mappings - version_map.new_version(); - - version_map - }; - - /// Static instance used for creating a 1:1 mapping between Firecracker release version - /// and snapshot data format version. - /// !CAVEAT! - /// This map is supposed to be strictly one-to-one (i.e. bijective) because - /// describe-snapshot inverts it to look up the release that matches the - /// snapshot version. If two versions map to the same snap_version, the - /// results are non-deterministic. - /// This means - /// - Do not insert patch releases here. - /// - Every minor version should be represented here. - /// - When requesting a `target_version`, these are the versions we expect. - pub static ref FC_VERSION_TO_SNAP_VERSION: HashMap = { - let mut mapping = HashMap::new(); - #[cfg(not(target_arch = "aarch64"))] - mapping.insert(Version::new(0, 23, 0), FC_V0_23_SNAP_VERSION); - - mapping.insert(Version::new(0, 24, 0), FC_V0_24_SNAP_VERSION); - mapping.insert(Version::new(0, 25, 0), FC_V0_25_SNAP_VERSION); - mapping.insert(Version::new(1, 0, 0), FC_V1_0_SNAP_VERSION); - mapping.insert(Version::new(1, 1, 0), FC_V1_1_SNAP_VERSION); - mapping.insert(Version::new(1, 2, 0), FC_V1_2_SNAP_VERSION); - mapping.insert(Version::new(1, 3, 0), FC_V1_3_SNAP_VERSION); - mapping.insert(Version::new(1, 4, 0), FC_V1_4_SNAP_VERSION); - mapping.insert(Version::new(1, 5, 0), FC_V1_5_SNAP_VERSION); - mapping.insert(Version::new(1, 6, 0), FC_V1_6_SNAP_VERSION); - mapping.insert(Version::new(1, 7, 0), FC_V1_7_SNAP_VERSION); - - mapping - }; -} diff --git a/src/vmm/src/vmm_config/boot_source.rs b/src/vmm/src/vmm_config/boot_source.rs index d5f6a1f3bde..e0b4019e318 100644 --- a/src/vmm/src/vmm_config/boot_source.rs +++ b/src/vmm/src/vmm_config/boot_source.rs @@ -5,8 +5,6 @@ use std::fs::File; use std::io; use serde::{Deserialize, Serialize}; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; /// Default guest kernel command line: /// - `reboot=k` shut down the guest on reboot, instead of well... rebooting; @@ -23,7 +21,7 @@ pub const DEFAULT_KERNEL_CMDLINE: &str = "reboot=k panic=1 pci=off nomodule 8250 /// Strongly typed data structure used to configure the boot source of the /// microvm. -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Versionize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct BootSourceConfig { /// Path of the kernel image. @@ -32,7 +30,6 @@ pub struct BootSourceConfig { pub initrd_path: Option, /// The boot arguments to pass to the kernel. If this field is uninitialized, /// DEFAULT_KERNEL_CMDLINE is used. - #[serde(skip_serializing_if = "Option::is_none")] pub boot_args: Option, } @@ -100,6 +97,7 @@ impl BootConfig { #[cfg(test)] pub(crate) mod tests { + use snapshot::Snapshot; use utils::tempfile::TempFile; use super::*; @@ -122,4 +120,18 @@ pub(crate) mod tests { [DEFAULT_KERNEL_CMDLINE.as_bytes(), &[b'\0']].concat() ); } + + #[test] + fn test_serde() { + let boot_src_cfg = BootSourceConfig { + boot_args: Some(DEFAULT_KERNEL_CMDLINE.to_string()), + initrd_path: Some("/tmp/initrd".to_string()), + kernel_image_path: "./vmlinux.bin".to_string(), + }; + + let mut snapshot_data = vec![0u8; 1000]; + Snapshot::serialize(&mut snapshot_data.as_mut_slice(), &boot_src_cfg).unwrap(); + let restored_boot_cfg = Snapshot::deserialize(&mut snapshot_data.as_slice()).unwrap(); + assert_eq!(boot_src_cfg, restored_boot_cfg); + } } diff --git a/src/vmm/src/vstate/memory.rs b/src/vmm/src/vstate/memory.rs index cce9a76f7d0..7dd15bc5118 100644 --- a/src/vmm/src/vstate/memory.rs +++ b/src/vmm/src/vstate/memory.rs @@ -8,9 +8,8 @@ use std::fs::File; use std::io::SeekFrom; +use serde::{Deserialize, Serialize}; use utils::{errno, get_page_size, u64_to_usize}; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; pub use vm_memory::bitmap::{AtomicBitmap, Bitmap, BitmapSlice, BS}; pub use vm_memory::mmap::MmapRegionBuilder; use vm_memory::mmap::{MmapRegionError, NewBitmap}; @@ -102,8 +101,7 @@ where } /// State of a guest memory region saved to file/buffer. -#[derive(Debug, PartialEq, Eq, Versionize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct GuestMemoryRegionState { // This should have been named `base_guest_addr` since it's _guest_ addr, but for // backward compatibility we have to keep this name. At least this comment should help. @@ -116,8 +114,7 @@ pub struct GuestMemoryRegionState { } /// Describes guest memory regions and their snapshot file mappings. -#[derive(Debug, Default, PartialEq, Eq, Versionize)] -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct GuestMemoryState { /// List of regions. pub regions: Vec, @@ -373,6 +370,7 @@ mod tests { use std::collections::HashMap; use std::io::{Read, Seek}; + use snapshot::Snapshot; use utils::get_page_size; use utils::tempfile::TempFile; @@ -525,6 +523,34 @@ mod tests { } } + fn check_serde(guest_memory: &GuestMemoryMmap) { + let mut snapshot_data = vec![0u8; 10000]; + let original_state = guest_memory.describe(); + Snapshot::serialize(&mut snapshot_data.as_mut_slice(), &original_state).unwrap(); + let restored_state = Snapshot::deserialize(&mut snapshot_data.as_slice()).unwrap(); + assert_eq!(original_state, restored_state); + } + + #[test] + fn test_serde() { + let page_size = get_page_size().unwrap(); + let region_size = page_size * 3; + + // Test with a single region + let guest_memory = + GuestMemoryMmap::from_raw_regions(&[(GuestAddress(0), region_size)], false).unwrap(); + check_serde(&guest_memory); + + // Test with some regions + let regions = vec![ + (GuestAddress(0), region_size), // pages 0-2 + (GuestAddress(region_size as u64), region_size), // pages 3-5 + (GuestAddress(region_size as u64 * 2), region_size), // pages 6-8 + ]; + let guest_memory = GuestMemoryMmap::from_raw_regions(®ions, true).unwrap(); + check_serde(&guest_memory); + } + #[test] fn test_describe() { let page_size: usize = get_page_size().unwrap(); diff --git a/src/vmm/src/vstate/vcpu/aarch64.rs b/src/vmm/src/vstate/vcpu/aarch64.rs index e7e928b3bd3..9c4eeb9b44d 100644 --- a/src/vmm/src/vstate/vcpu/aarch64.rs +++ b/src/vmm/src/vstate/vcpu/aarch64.rs @@ -9,12 +9,10 @@ use std::fmt::{Debug, Write}; use kvm_bindings::*; use kvm_ioctls::*; -use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult}; -use versionize_derive::Versionize; +use serde::{Deserialize, Serialize}; use crate::arch::aarch64::regs::{ - arm64_core_reg_id, offset__of, Aarch64RegisterOld, Aarch64RegisterRef, Aarch64RegisterVec, - KVM_REG_ARM_TIMER_CNT, + arm64_core_reg_id, offset__of, Aarch64RegisterVec, KVM_REG_ARM_TIMER_CNT, }; use crate::arch::aarch64::vcpu::{ get_all_registers, get_all_registers_ids, get_mpidr, get_mpstate, get_registers, set_mpstate, @@ -252,15 +250,11 @@ impl KvmVcpu { } /// Structure holding VCPU kvm state. -#[derive(Default, Clone, Versionize)] +#[derive(Default, Clone, Serialize, Deserialize)] pub struct VcpuState { /// Multiprocessing state. pub mp_state: kvm_bindings::kvm_mp_state, - /// Old representation of Vcpu registers. - #[version(end = 2, default_fn = "default_old_regs")] - pub old_regs: Vec, /// Vcpu registers. - #[version(start = 2, de_fn = "de_regs")] pub regs: Aarch64RegisterVec, /// We will be using the mpidr for passing it to the VmState. /// The VmState will give this away for saving restoring the icc and redistributor @@ -269,7 +263,6 @@ pub struct VcpuState { /// kvi states for vcpu initialization. /// If None then use `default_kvi` to obtain /// kvi. - #[version(start = 2, default_fn = "default_kvi")] pub kvi: Option, } @@ -295,28 +288,6 @@ impl Debug for VcpuState { } } -impl VcpuState { - fn default_old_regs(_: u16) -> Vec { - Vec::default() - } - - fn default_kvi(_: u16) -> Option { - None - } - - fn de_regs(&mut self, _source_version: u16) -> VersionizeResult<()> { - let mut regs = Aarch64RegisterVec::default(); - for reg in self.old_regs.iter() { - let reg_ref: Aarch64RegisterRef = reg - .try_into() - .map_err(|e: &str| VersionizeError::Deserialize(e.into()))?; - regs.push(reg_ref); - } - self.regs = regs; - Ok(()) - } -} - #[cfg(test)] mod tests { #![allow(clippy::undocumented_unsafe_blocks)] diff --git a/src/vmm/src/vstate/vcpu/x86_64.rs b/src/vmm/src/vstate/vcpu/x86_64.rs index d14d509d67f..941e13b52f8 100644 --- a/src/vmm/src/vstate/vcpu/x86_64.rs +++ b/src/vmm/src/vstate/vcpu/x86_64.rs @@ -14,8 +14,7 @@ use kvm_bindings::{ }; use kvm_ioctls::{VcpuExit, VcpuFd}; use log::{error, warn}; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; +use serde::{Deserialize, Serialize}; use crate::arch::x86_64::interrupts; use crate::arch::x86_64::msr::{create_boot_msr_entries, MsrError}; @@ -403,7 +402,6 @@ impl KvmVcpu { Ok(VcpuState { cpuid, saved_msrs, - msrs: Msrs::new(0)?, debug_regs, lapic, mp_state, @@ -543,17 +541,12 @@ impl KvmVcpu { } } -#[derive(Clone, Versionize)] /// Structure holding VCPU kvm state. -// NOTICE: Any changes to this structure require a snapshot version bump. +#[derive(Clone, Serialize, Deserialize)] pub struct VcpuState { /// CpuId. pub cpuid: CpuId, - /// Msrs. - #[version(end = 3, default_fn = "default_msrs")] - pub msrs: Msrs, /// Saved msrs. - #[version(start = 3, de_fn = "de_saved_msrs")] pub saved_msrs: Vec, /// Debug regs. pub debug_regs: kvm_debugregs, @@ -572,7 +565,6 @@ pub struct VcpuState { /// Xsave. pub xsave: kvm_xsave, /// Tsc khz. - #[version(start = 2, default_fn = "default_tsc_khz")] pub tsc_khz: Option, } @@ -585,7 +577,6 @@ impl Debug for VcpuState { } f.debug_struct("VcpuState") .field("cpuid", &self.cpuid) - .field("msrs", &self.msrs) .field("saved_msrs", &debug_kvm_regs) .field("debug_regs", &self.debug_regs) .field("lapic", &self.lapic) @@ -600,26 +591,6 @@ impl Debug for VcpuState { } } -impl VcpuState { - fn default_tsc_khz(_: u16) -> Option { - warn!("CPU TSC freq not found in snapshot"); - None - } - - fn default_msrs(_source_version: u16) -> Msrs { - // Safe to unwrap since Msrs::new() only returns an error if the number - // of elements exceeds KVM_MAX_MSR_ENTRIES - Msrs::new(0).unwrap() - } - - fn de_saved_msrs(&mut self, source_version: u16) -> VersionizeResult<()> { - if source_version < 3 { - self.saved_msrs.push(self.msrs.clone()); - } - Ok(()) - } -} - #[cfg(test)] mod tests { #![allow(clippy::undocumented_unsafe_blocks)] @@ -642,7 +613,6 @@ mod tests { fn default() -> Self { VcpuState { cpuid: CpuId::new(1).unwrap(), - msrs: Msrs::new(1).unwrap(), saved_msrs: vec![Msrs::new(1).unwrap()], debug_regs: Default::default(), lapic: Default::default(), diff --git a/src/vmm/src/vstate/vm.rs b/src/vmm/src/vstate/vm.rs index 491dd306c1d..da2ec4eac6a 100644 --- a/src/vmm/src/vstate/vm.rs +++ b/src/vmm/src/vstate/vm.rs @@ -16,10 +16,9 @@ use kvm_bindings::{ }; use kvm_bindings::{kvm_userspace_memory_region, KVM_API_VERSION, KVM_MEM_LOG_DIRTY_PAGES}; use kvm_ioctls::{Kvm, VmFd}; +use serde::{Deserialize, Serialize}; #[cfg(target_arch = "x86_64")] use utils::u64_to_usize; -use versionize::{VersionMap, Versionize, VersionizeResult}; -use versionize_derive::Versionize; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::gic::GICDevice; @@ -316,12 +315,11 @@ impl Vm { /// Structure holding an general specific VM state. #[cfg(target_arch = "aarch64")] -#[derive(Debug, Default, Versionize)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct VmState { /// GIC state. pub gic: GicState, /// Additional capabilities that were specified in cpu template. - #[version(start = 2, default_fn = "default_caps")] pub kvm_cap_modifiers: Vec, } @@ -439,9 +437,8 @@ impl Vm { } #[cfg(target_arch = "x86_64")] -#[derive(Default, Versionize)] +#[derive(Default, Deserialize, Serialize)] /// Structure holding VM kvm state. -// NOTICE: Any changes to this structure require a snapshot version bump. pub struct VmState { pitstate: kvm_pit_state2, clock: kvm_clock_data, @@ -452,16 +449,9 @@ pub struct VmState { ioapic: kvm_irqchip, /// Additional capabilities that were specified in cpu template. - #[version(start = 2, default_fn = "default_caps")] pub kvm_cap_modifiers: Vec, } -impl VmState { - fn default_caps(_: u16) -> Vec { - Vec::default() - } -} - #[cfg(target_arch = "x86_64")] impl fmt::Debug for VmState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -478,6 +468,9 @@ impl fmt::Debug for VmState { #[cfg(test)] pub(crate) mod tests { + #[cfg(target_arch = "x86_64")] + use snapshot::Snapshot; + use super::*; use crate::vstate::memory::{GuestAddress, GuestMemoryExtension, GuestMemoryMmap}; @@ -579,6 +572,20 @@ pub(crate) mod tests { vm.restore_state(&vm_state).unwrap_err(); } + #[cfg(target_arch = "x86_64")] + #[test] + fn test_vmstate_serde() { + let mut snapshot_data = vec![0u8; 10000]; + + let (mut vm, _) = setup_vm(0x1000); + vm.setup_irqchip().unwrap(); + let state = vm.save_state().unwrap(); + Snapshot::serialize(&mut snapshot_data.as_mut_slice(), &state).unwrap(); + let restored_state: VmState = Snapshot::deserialize(&mut snapshot_data.as_slice()).unwrap(); + + vm.restore_state(&restored_state).unwrap(); + } + #[test] fn test_set_kvm_memory_regions() { let vm = Vm::new(vec![]).expect("Cannot create new vm"); diff --git a/src/vmm/tests/integration_tests.rs b/src/vmm/tests/integration_tests.rs index 5b328c791ce..7489764bc5a 100644 --- a/src/vmm/tests/integration_tests.rs +++ b/src/vmm/tests/integration_tests.rs @@ -15,7 +15,6 @@ use vmm::utilities::mock_resources::{MockVmResources, NOISY_KERNEL_IMAGE}; #[cfg(target_arch = "x86_64")] use vmm::utilities::test_utils::dirty_tracking_vmm; use vmm::utilities::test_utils::{create_vmm, default_vmm, default_vmm_no_boot}; -use vmm::version_map::VERSION_MAP; use vmm::vmm_config::instance_info::{InstanceInfo, VmState}; use vmm::vmm_config::snapshot::{CreateSnapshotParams, SnapshotType}; use vmm::{DumpCpuConfigError, EventManager, FcExitCode}; @@ -197,13 +196,7 @@ fn verify_create_snapshot(is_diff: bool) -> (TempFile, TempFile) { { let mut locked_vmm = vmm.lock().unwrap(); - persist::create_snapshot( - &mut locked_vmm, - &vm_info, - &snapshot_params, - VERSION_MAP.clone(), - ) - .unwrap(); + persist::create_snapshot(&mut locked_vmm, &vm_info, &snapshot_params).unwrap(); } vmm.lock().unwrap().stop(FcExitCode::Ok); @@ -212,12 +205,8 @@ fn verify_create_snapshot(is_diff: bool) -> (TempFile, TempFile) { let snapshot_path = snapshot_file.as_path().to_path_buf(); let snapshot_file_metadata = std::fs::metadata(snapshot_path).unwrap(); let snapshot_len = snapshot_file_metadata.len() as usize; - let (restored_microvm_state, _) = Snapshot::load::<_, MicrovmState>( - &mut snapshot_file.as_file(), - snapshot_len, - VERSION_MAP.clone(), - ) - .unwrap(); + let (restored_microvm_state, _) = + Snapshot::load::<_, MicrovmState>(&mut snapshot_file.as_file(), snapshot_len).unwrap(); assert_eq!(restored_microvm_state.vm_info, vm_info); @@ -247,12 +236,8 @@ fn verify_load_snapshot(snapshot_file: TempFile, memory_file: TempFile) { let snapshot_file_metadata = snapshot_file.as_file().metadata().unwrap(); let snapshot_len = snapshot_file_metadata.len() as usize; snapshot_file.as_file().seek(SeekFrom::Start(0)).unwrap(); - let (microvm_state, _) = Snapshot::load::<_, MicrovmState>( - &mut snapshot_file.as_file(), - snapshot_len, - VERSION_MAP.clone(), - ) - .unwrap(); + let (microvm_state, _) = + Snapshot::load::<_, MicrovmState>(&mut snapshot_file.as_file(), snapshot_len).unwrap(); let mem = GuestMemoryMmap::from_state( Some(memory_file.as_file()), µvm_state.memory_state, @@ -351,11 +336,6 @@ fn get_microvm_state_from_snapshot() -> MicrovmState { let snapshot_file_metadata = snapshot_file.as_file().metadata().unwrap(); let snapshot_len = snapshot_file_metadata.len() as usize; snapshot_file.as_file().seek(SeekFrom::Start(0)).unwrap(); - let (state, _) = Snapshot::load( - &mut snapshot_file.as_file(), - snapshot_len, - VERSION_MAP.clone(), - ) - .unwrap(); + let (state, _) = Snapshot::load(&mut snapshot_file.as_file(), snapshot_len).unwrap(); state } diff --git a/tests/framework/artifacts.py b/tests/framework/artifacts.py index b841a109b9d..3aa564b4a7c 100644 --- a/tests/framework/artifacts.py +++ b/tests/framework/artifacts.py @@ -13,7 +13,7 @@ from framework.defs import ARTIFACT_DIR from framework.properties import global_props -from framework.utils import get_firecracker_version_from_toml +from framework.utils import get_firecracker_version_from_toml, run_cmd from host_tools.cargo_build import get_binary @@ -98,7 +98,21 @@ def version_tuple(self): @property def snapshot_version_tuple(self): """Return the artifact's snapshot version as a tuple: `X.Y.0`.""" - return self.version_tuple[:2] + (0,) + + # Starting from Firecracker v1.6.0, snapshots have their own version that is + # independent of Firecracker versions. For these Firecracker versions, use + # the --snapshot-version Firecracker flag, to figure out which snapshot version + # it supports. + # TODO: remove this check once all version prior to 1.6.0 go out of support. + if packaging.version.parse(self.version) < packaging.version.parse("1.7.0"): + return self.version_tuple[:2] + (0,) + + return ( + run_cmd([self.path, "--snapshot-version"]) + .stdout.strip() + .split("\n")[0] + .split(".") + ) @property def snapshot_version(self): diff --git a/tests/integration_tests/functional/test_api.py b/tests/integration_tests/functional/test_api.py index 1f398da1cd8..ac5a16d0762 100644 --- a/tests/integration_tests/functional/test_api.py +++ b/tests/integration_tests/functional/test_api.py @@ -1193,6 +1193,7 @@ def test_get_full_config_after_restoring_snapshot(microvm_factory, uvm_nano): expected_cfg["boot-source"] = { "kernel_image_path": uvm_nano.get_jailed_resource(uvm_nano.kernel_file), "initrd_path": None, + "boot_args": None, } # no ipv4 specified during PUT /mmds/config so we expect the default diff --git a/tests/integration_tests/functional/test_cmd_line_parameters.py b/tests/integration_tests/functional/test_cmd_line_parameters.py index 3bd08877b60..62cfdc304fe 100644 --- a/tests/integration_tests/functional/test_cmd_line_parameters.py +++ b/tests/integration_tests/functional/test_cmd_line_parameters.py @@ -5,6 +5,7 @@ import subprocess from pathlib import Path +import packaging.version import pytest from framework.utils import run_cmd @@ -20,6 +21,15 @@ def test_describe_snapshot_all_versions( For each release create a snapshot and verify the data version of the snapshot state file. """ + + # TODO: remove this check once all versions prior to 1.6.0 go out of support. + if packaging.version.parse(firecracker_release.version) < packaging.version.parse( + "1.7.0" + ): + pytest.skip( + "We can't parse snapshot files created from Firecracker with version < 1.7.0." + ) + target_version = firecracker_release.snapshot_version vm = microvm_factory.build( guest_kernel, diff --git a/tests/integration_tests/functional/test_mmds.py b/tests/integration_tests/functional/test_mmds.py index 2a682d24fc8..1673008d11b 100644 --- a/tests/integration_tests/functional/test_mmds.py +++ b/tests/integration_tests/functional/test_mmds.py @@ -10,7 +10,6 @@ import pytest from framework.artifacts import working_version_as_artifact -from framework.properties import global_props from framework.utils import ( configure_mmds, generate_mmds_get_request, @@ -612,39 +611,6 @@ def test_mmds_snapshot(uvm_nano, microvm_factory, version): ) -def test_mmds_older_snapshot( - microvm_factory, guest_kernel, rootfs, firecracker_release -): - """ - Test MMDS behavior restoring older snapshots in the current version. - - Ensures that the MMDS version is persisted or initialised with the default - if the FC version does not support this feature. - """ - - # due to bug fixed in commit 8dab78b - firecracker_version = firecracker_release.version_tuple - if global_props.instance == "m6a.metal" and firecracker_version < (1, 3, 3): - pytest.skip("incompatible with AMD and Firecracker <1.3.3") - - microvm = microvm_factory.build( - guest_kernel, - rootfs, - fc_binary_path=firecracker_release.path, - jailer_binary_path=firecracker_release.jailer, - ) - microvm.spawn() - microvm.basic_config() - microvm.add_net_iface() - - mmds_version = "V2" - _validate_mmds_snapshot( - microvm, - microvm_factory, - mmds_version, - ) - - def test_mmds_v2_negative(test_microvm_with_api): """ Test invalid MMDS GET/PUT requests when using V2. diff --git a/tests/integration_tests/functional/test_snapshot_advanced.py b/tests/integration_tests/functional/test_snapshot_advanced.py deleted file mode 100644 index 2bd26cd56c3..00000000000 --- a/tests/integration_tests/functional/test_snapshot_advanced.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Advanced tests scenarios for snapshot save/restore.""" - -import tempfile - -import pytest -from test_balloon import _test_rss_memory_lower - -import host_tools.drive as drive_tools -from framework.microvm import SnapshotType -from framework.properties import global_props - -# Define 4 scratch drives. -scratch_drives = ["vdb", "vdc", "vdd", "vde"] - - -def test_restore_old_to_current( - microvm_factory, guest_kernel, rootfs_ubuntu_22, firecracker_release -): - """ - Restore snapshots from previous supported versions of Firecracker. - - For each firecracker release: - 1. Snapshot with the past release - 2. Restore with the current build - """ - - # due to bug fixed in commit 8dab78b - firecracker_version = firecracker_release.version_tuple - if global_props.instance == "m6a.metal" and firecracker_version < (1, 3, 3): - pytest.skip("incompatible with AMD and Firecracker <1.3.3") - - # Microvm: 2vCPU 256MB RAM, balloon, 4 disks and 4 net devices. - diff_snapshots = True - vm = microvm_factory.build( - guest_kernel, - rootfs_ubuntu_22, - fc_binary_path=firecracker_release.path, - jailer_binary_path=firecracker_release.jailer, - ) - vm.spawn() - vm.basic_config(track_dirty_pages=True) - snapshot = create_snapshot_helper( - vm, - drives=scratch_drives, - diff_snapshots=diff_snapshots, - balloon=diff_snapshots, - ) - vm = microvm_factory.build() - vm.spawn() - vm.restore_from_snapshot(snapshot, resume=True) - validate_all_devices(vm, diff_snapshots) - print(vm.log_data) - - -def validate_all_devices(microvm, balloon): - """Perform a basic validation for all devices of a microvm.""" - # Test that net devices have connectivity after restore. - for iface in microvm.iface.values(): - print("Testing net device", iface["iface"].dev_name) - microvm.guest_ip = iface["iface"].guest_ip - exit_code, _, _ = microvm.ssh.run("sync") - - # Drop page cache. - # Ensure further reads are going to be served from emulation layer. - cmd = "sync; echo 1 > /proc/sys/vm/drop_caches" - exit_code, _, _ = microvm.ssh.run(cmd) - assert exit_code == 0 - - # Validate checksum of /dev/vdX/test. - # Should be ab893875d697a3145af5eed5309bee26 for 10 pages - # of zeroes. - for drive in list(microvm.disks)[1:]: - # Mount block device. - print("Testing drive ", drive) - cmd = f"mkdir -p /tmp/{drive} ; mount /dev/{drive} /tmp/{drive}" - exit_code, _, _ = microvm.ssh.run(cmd) - assert exit_code == 0 - - # Validate checksum. - cmd = f"md5sum /tmp/{drive}/test | cut -d ' ' -f 1" - exit_code, stdout, _ = microvm.ssh.run(cmd) - assert exit_code == 0 - assert stdout.strip() == "ab893875d697a3145af5eed5309bee26" - print("* checksum OK.") - - if balloon is True: - print("Testing balloon memory reclaim.") - # Call helper fn from balloon integration tests. - _test_rss_memory_lower(microvm) - - -def create_snapshot_helper( - vm, - drives=None, - balloon=False, - diff_snapshots=False, -): - """Create a snapshot with many devices.""" - if diff_snapshots is False: - snapshot_type = SnapshotType.FULL - else: - # Version 0.24 and greater has Diff and balloon support. - snapshot_type = SnapshotType.DIFF - - if balloon: - # Add a memory balloon with stats enabled. - vm.api.balloon.put( - amount_mib=0, deflate_on_oom=True, stats_polling_interval_s=1 - ) - - test_drives = [] if drives is None else drives - - # Add disks. - for scratch in test_drives: - # Add a scratch 64MB RW non-root block device. - scratchdisk = drive_tools.FilesystemFile(tempfile.mktemp(), size=64) - vm.add_drive(scratch, scratchdisk.path) - - # Workaround FilesystemFile destructor removal of file. - scratchdisk.path = None - - for _ in range(4): - vm.add_net_iface() - - vm.start() - - # Iterate and validate connectivity on all ifaces after boot. - for i in range(4): - exit_code, _, _ = vm.ssh_iface(i).run("sync") - assert exit_code == 0 - - # Mount scratch drives in guest. - for blk in test_drives: - # Create mount point and mount each device. - cmd = f"mkdir -p /tmp/mnt/{blk} && mount /dev/{blk} /tmp/mnt/{blk}" - exit_code, _, _ = vm.ssh.run(cmd) - assert exit_code == 0 - - # Create file using dd using O_DIRECT. - # After resume we will compute md5sum on these files. - dd = f"dd if=/dev/zero of=/tmp/mnt/{blk}/test bs=4096 count=10 oflag=direct" - exit_code, _, _ = vm.ssh.run(dd) - assert exit_code == 0 - - # Unmount the device. - cmd = f"umount /dev/{blk}" - exit_code, _, _ = vm.ssh.run(cmd) - assert exit_code == 0 - - snapshot = vm.make_snapshot(snapshot_type) - print("========== Firecracker create snapshot log ==========") - print(vm.log_data) - vm.kill() - return snapshot diff --git a/tests/integration_tests/functional/test_snapshot_basic.py b/tests/integration_tests/functional/test_snapshot_basic.py index b0f3a3a40f4..ec6c5217b8d 100644 --- a/tests/integration_tests/functional/test_snapshot_basic.py +++ b/tests/integration_tests/functional/test_snapshot_basic.py @@ -13,12 +13,7 @@ import host_tools.drive as drive_tools from framework.microvm import SnapshotType -from framework.utils import ( - check_filesystem, - get_firecracker_version_from_toml, - run_cmd, - wait_process_termination, -) +from framework.utils import check_filesystem, run_cmd, wait_process_termination from framework.utils_vsock import ( ECHO_SERVER_PORT, VSOCK_UDS_PATH, @@ -53,18 +48,20 @@ def test_snapshot_current_version(uvm_nano): vm = uvm_nano vm.start() - version = get_firecracker_version_from_toml() - # normalize to a snapshot version - version = f"{version.major}.{version.minor}.0" snapshot = vm.snapshot_full() # Fetch Firecracker binary for the latest version fc_binary = uvm_nano.fc_binary_path + # Get supported snapshot version from Firecracker binary + snapshot_version = ( + run_cmd(f"{fc_binary} --snapshot-version").stdout.strip().splitlines()[0] + ) + # Verify the output of `--describe-snapshot` command line parameter cmd = [str(fc_binary)] + ["--describe-snapshot", str(snapshot.vmstate)] _, stdout, _ = run_cmd(cmd) - assert version in stdout + assert snapshot_version in stdout # Testing matrix: diff --git a/tests/integration_tests/performance/test_snapshot_perf.py b/tests/integration_tests/performance/test_snapshot_perf.py index bf4f04a1b0b..1fd331f3c5f 100644 --- a/tests/integration_tests/performance/test_snapshot_perf.py +++ b/tests/integration_tests/performance/test_snapshot_perf.py @@ -2,10 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 """Basic tests scenarios for snapshot save/restore.""" -import pytest - -from framework.properties import global_props - # How many latencies do we sample per test. SAMPLE_COUNT = 3 USEC_IN_MSEC = 1000 @@ -23,84 +19,6 @@ def snapshot_create_producer(vm): return value -def snapshot_resume_producer(microvm_factory, snapshot): - """Produce results for snapshot resume tests.""" - - microvm = microvm_factory.build() - microvm.spawn() - microvm.restore_from_snapshot(snapshot, resume=True) - - # Attempt to connect to resumed microvm. - # Verify if guest can run commands. - exit_code, _, _ = microvm.ssh.run("ls") - assert exit_code == 0 - - value = 0 - # Parse all metric data points in search of load_snapshot time. - metrics = microvm.get_all_metrics() - for data_point in metrics: - cur_value = data_point["latencies_us"]["load_snapshot"] / USEC_IN_MSEC - if cur_value > 0: - value = cur_value - break - - print("Latency {value} ms") - return value - - -def test_older_snapshot_resume_latency( - microvm_factory, - guest_kernel_linux_4_14, - rootfs, - firecracker_release, - io_engine, - metrics, -): - """ - Test scenario: Older snapshot load performance measurement. - - With each previous firecracker version, create a snapshot and try to - restore in current version. - """ - - # due to bug fixed in commit 8dab78b - firecracker_version = firecracker_release.version_tuple - if global_props.instance == "m6a.metal" and firecracker_version < (1, 3, 3): - pytest.skip("incompatible with AMD and Firecracker <1.3.3") - - vm = microvm_factory.build( - guest_kernel_linux_4_14, - rootfs, - monitor_memory=False, - fc_binary_path=firecracker_release.path, - jailer_binary_path=firecracker_release.jailer, - ) - vm.spawn() - vm.basic_config(vcpu_count=2, mem_size_mib=512) - vm.add_net_iface() - vm.start() - # Check if guest works. - exit_code, _, _ = vm.ssh.run("ls") - assert exit_code == 0 - snapshot = vm.snapshot_full() - - metrics.set_dimensions( - { - **vm.dimensions, - "io_engine": io_engine, - "performance_test": "test_older_snapshot_resume_latency", - "firecracker_version": firecracker_release.name, - } - ) - - for _ in range(SAMPLE_COUNT): - metrics.put_metric( - "latency", - snapshot_resume_producer(microvm_factory, snapshot), - "Milliseconds", - ) - - def test_snapshot_create_latency( microvm_factory, guest_kernel_linux_4_14, diff --git a/tools/bump-version.sh b/tools/bump-version.sh index cecbd11129e..b3244581eb0 100755 --- a/tools/bump-version.sh +++ b/tools/bump-version.sh @@ -26,17 +26,6 @@ fi version=$1 -function check_snapshot_version { - local version=$1 - local snap_version=$(echo $version |cut -f-2 -d. |tr . _) - if ! grep -s FC_V${snap_version}_SNAP_VERSION src/vmm/src/version_map.rs; then - die "I couldn't find FC_V${snap_version}_SNAP_VERSION in src/vmm/src/version_map.rs" - fi -} - -check_snapshot_version "$version" - - # Get current version from the swagger spec. prev_ver=$(get_swagger_version)