From 6326af20e3436f5ff9db8151393fc4b354eb0759 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Wed, 2 Aug 2023 22:41:52 +0800 Subject: [PATCH 1/4] kata-types: introduce KataVirtualVolume Introduce structure KataVirtualVolume to to encapsulate information for extra mount options and direct volumes, so we could build a common infrastructure to handle these cases. Fixes: #7699 Signed-off-by: Jiang Liu --- src/libs/kata-types/src/mount.rs | 180 ++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 3 deletions(-) diff --git a/src/libs/kata-types/src/mount.rs b/src/libs/kata-types/src/mount.rs index 83bcca3ea8..6a714ed3af 100644 --- a/src/libs/kata-types/src/mount.rs +++ b/src/libs/kata-types/src/mount.rs @@ -34,6 +34,23 @@ pub const SANDBOX_BIND_MOUNTS_RO: &str = ":ro"; /// SANDBOX_BIND_MOUNTS_RO is for sandbox bindmounts with readwrite pub const SANDBOX_BIND_MOUNTS_RW: &str = ":rw"; +/// Directly assign a block volume to vm and mount it inside guest. +pub const KATA_VIRTUAL_VOLUME_DIRECT_BLOCK: &str = "direct_block"; +/// Present a container image as a generic block device. +pub const KATA_VIRTUAL_VOLUME_IMAGE_RAW_BLOCK: &str = "image_raw_block"; +/// Present each container image layer as a generic block device. +pub const KATA_VIRTUAL_VOLUME_LAYER_RAW_BLOCK: &str = "layer_raw_block"; +/// Present a container image as a nydus block device. +pub const KATA_VIRTUAL_VOLUME_IMAGE_NYDUS_BLOCK: &str = "image_nydus_block"; +/// Present each container image layer as a nydus block device. +pub const KATA_VIRTUAL_VOLUME_LAYER_NYDUS_BLOCK: &str = "layer_nydus_block"; +/// Present a container image as a nydus filesystem. +pub const KATA_VIRTUAL_VOLUME_IMAGE_NYDUS_FS: &str = "image_nydus_fs"; +/// Present each container image layer as a nydus filesystem. +pub const KATA_VIRTUAL_VOLUME_LAYER_NYDUS_FS: &str = "layer_nydus_fs"; +/// Download and extra container image inside guest vm. +pub const KATA_VIRTUAL_VOLUME_IMAGE_GUEST_PULL: &str = "image_guest_pull"; + /// Information about a mount. #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct Mount { @@ -66,7 +83,7 @@ impl Mount { /// DirectVolumeMountInfo contains the information needed by Kata /// to consume a host block device and mount it as a filesystem inside the guest VM. -#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] pub struct DirectVolumeMountInfo { /// The type of the volume (ie. block) pub volume_type: String, @@ -80,8 +97,133 @@ pub struct DirectVolumeMountInfo { pub options: Vec, } -/// join_path joins user provided volumepath with kata direct-volume root path -/// the volume_path is base64-encoded and then safely joined to the end of path prefix +/// Configuration information for DmVerity device. +#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct DmVerityInfo { + /// Hash algorithm for dm-verity. + pub hashtype: String, + /// Root hash for device verification or activation. + pub hash: String, + /// Size of data device used in verification. + pub blocknum: u64, + /// Used block size for the data device. + pub blocksize: u64, + /// Used block size for the hash device. + pub hashsize: u64, + /// Offset of hash area/superblock on hash_device. + pub offset: u64, +} + +/// Information about directly assigned volume. +#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct DirectAssignedVolume { + /// Meta information for directly assigned volume. + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub metadata: HashMap, +} + +/// Information about pulling image inside guest. +#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct ImagePullVolume { + /// Meta information for pulling image inside guest. + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub metadata: HashMap, +} + +/// Information about nydus image volume. +#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct NydusImageVolume { + /// Nydus configuration information. + #[serde(default, skip_serializing_if = "String::is_empty")] + pub config: String, + + /// Nydus snapshot directory + #[serde(default, skip_serializing_if = "String::is_empty")] + pub snapshot_dir: String, +} + +/// Kata virtual volume to encapsulate information for extra mount options and direct volumes. +/// +/// It's very expensive to build direct communication channels to pass information: +/// - between snapshotters and kata-runtime/kata-agent/image-rs +/// - between CSI drivers and kata-runtime/kata-agent +/// +/// So `KataVirtualVolume` is introduced to encapsulate extra mount options and direct volume +/// information, so we can build a common infrastructure to handle them. +/// `KataVirtualVolume` is a superset of `NydusExtraOptions` and `DirectVolumeMountInfo`. +/// +/// Value of `volume_type` determines how to interpret other fields in the structure. +/// +/// - `KATA_VIRTUAL_VOLUME_IGNORE` +/// -- all other fields should be ignored/unused. +/// +/// - `KATA_VIRTUAL_VOLUME_DIRECT_BLOCK` +/// -- `source`: the directly assigned block device +/// -- `fs_type`: filesystem type +/// -- `options`: mount options +/// -- `direct_volume`: additional metadata to pass to the agent regarding this volume. +/// +/// - `KATA_VIRTUAL_VOLUME_IMAGE_RAW_BLOCK` or `KATA_VIRTUAL_VOLUME_LAYER_RAW_BLOCK` +/// -- `source`: path to the raw block image for the container image or layer. +/// -- `fs_type`: filesystem type +/// -- `options`: mount options +/// -- `dm_verity`: disk dm-verity information +/// +/// - `KATA_VIRTUAL_VOLUME_IMAGE_NYDUS_BLOCK` or `KATA_VIRTUAL_VOLUME_LAYER_NYDUS_BLOCK` +/// -- `source`: path to nydus meta blob +/// -- `fs_type`: filesystem type +/// -- `nydus_image`: configuration information for nydus image. +/// -- `dm_verity`: disk dm-verity information +/// +/// - `KATA_VIRTUAL_VOLUME_IMAGE_NYDUS_FS` or `KATA_VIRTUAL_VOLUME_LAYER_NYDUS_FS` +/// -- `source`: path to nydus meta blob +/// -- `fs_type`: filesystem type +/// -- `nydus_image`: configuration information for nydus image. +/// +/// - `KATA_VIRTUAL_VOLUME_IMAGE_GUEST_PULL` +/// -- `source`: image reference +/// -- `image_pull`: metadata for image pulling +#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct KataVirtualVolume { + /// Type of virtual volume. + pub volume_type: String, + /// Source/device path for the virtual volume. + #[serde(default, skip_serializing_if = "String::is_empty")] + pub source: String, + /// Filesystem type. + #[serde(default, skip_serializing_if = "String::is_empty")] + pub fs_type: String, + /// Mount options. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub options: Vec, + + /// Information about directly assigned volume. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub direct_volume: Option, + /// Information about pulling image inside guest. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub image_pull: Option, + /// Information about nydus image volume. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nydus_image: Option, + /// DmVerity: configuration information + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dm_verity: Option, +} + +impl KataVirtualVolume { + /// Create a new instance of `KataVirtualVolume` with specified type. + pub fn new(volume_type: String) -> Self { + Self { + volume_type, + ..Default::default() + } + } +} + +/// Join user provided volume path with kata direct-volume root path. +/// +/// The `volume_path` is base64-encoded and then safely joined to the `prefix` pub fn join_path(prefix: &str, volume_path: &str) -> Result { if volume_path.is_empty() { return Err(anyhow!("volume path must not be empty")); @@ -242,4 +384,36 @@ mod tests { ); assert_eq!(extra_option.fs_version, "v6"); } + + #[test] + fn test_kata_virtual_volume() { + let mut volume = KataVirtualVolume::new(KATA_VIRTUAL_VOLUME_DIRECT_BLOCK.to_string()); + assert_eq!( + volume.volume_type.as_str(), + KATA_VIRTUAL_VOLUME_DIRECT_BLOCK + ); + assert!(volume.fs_type.is_empty()); + + let value = serde_json::to_string(&volume).unwrap(); + assert_eq!(&value, "{\"volume_type\":\"direct_block\"}"); + + volume.source = "/tmp".to_string(); + volume.fs_type = "ext4".to_string(); + volume.options = vec!["rw".to_string()]; + volume.nydus_image = Some(NydusImageVolume { + config: "test".to_string(), + snapshot_dir: "/var/lib/nydus.dir".to_string(), + }); + let mut metadata = HashMap::new(); + metadata.insert("mode".to_string(), "rw".to_string()); + volume.direct_volume = Some(DirectAssignedVolume { metadata }); + + let value = serde_json::to_string(&volume).unwrap(); + let volume2: KataVirtualVolume = serde_json::from_str(&value).unwrap(); + assert_eq!(volume.volume_type, volume2.volume_type); + assert_eq!(volume.source, volume2.source); + assert_eq!(volume.fs_type, volume2.fs_type); + assert_eq!(volume.nydus_image, volume2.nydus_image); + assert_eq!(volume.direct_volume, volume2.direct_volume); + } } From fa2fdc10572a4449c41876e59ed490c9bad841d3 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Sun, 20 Aug 2023 16:16:11 +0800 Subject: [PATCH 2/4] kata-types: implement two conversion helpers for KataVirtualVolume Enable conversions from NydusExtraOptions/DirectVolumeMountInfo to KataVirtualVolume. Signed-off-by: Jiang Liu --- src/libs/kata-types/src/mount.rs | 188 ++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 41 deletions(-) diff --git a/src/libs/kata-types/src/mount.rs b/src/libs/kata-types/src/mount.rs index 6a714ed3af..7a60646f3a 100644 --- a/src/libs/kata-types/src/mount.rs +++ b/src/libs/kata-types/src/mount.rs @@ -4,7 +4,8 @@ // SPDX-License-Identifier: Apache-2.0 // -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Context, Error, Result}; +use std::convert::TryFrom; use std::{collections::HashMap, fs, path::PathBuf}; /// Prefix to mark a volume as Kata special. @@ -97,6 +98,44 @@ pub struct DirectVolumeMountInfo { pub options: Vec, } +/// Nydus extra options +#[derive(Debug, serde::Deserialize)] +pub struct NydusExtraOptions { + /// source path + pub source: String, + /// nydus config + pub config: String, + /// snapshotter directory + #[serde(rename(deserialize = "snapshotdir"))] + pub snapshot_dir: String, + /// fs version + pub fs_version: String, +} + +impl NydusExtraOptions { + /// Create Nydus extra options + pub fn new(mount: &Mount) -> Result { + let options: Vec<&str> = mount + .options + .iter() + .filter(|x| x.starts_with("extraoption=")) + .map(|x| x.as_ref()) + .collect(); + + if options.len() != 1 { + return Err(anyhow!( + "get_nydus_extra_options: Invalid nydus options: {:?}", + &mount.options + )); + } + let config_raw_data = options[0].trim_start_matches("extraoption="); + let extra_options_buf = + base64::decode(config_raw_data).context("decode the nydus's base64 extraoption")?; + + serde_json::from_slice(&extra_options_buf).context("deserialize nydus's extraoption") + } +} + /// Configuration information for DmVerity device. #[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] pub struct DmVerityInfo { @@ -221,6 +260,59 @@ impl KataVirtualVolume { } } +impl TryFrom<&DirectVolumeMountInfo> for KataVirtualVolume { + type Error = Error; + + fn try_from(value: &DirectVolumeMountInfo) -> std::result::Result { + let volume_type = match value.volume_type.as_str() { + "block" => KATA_VIRTUAL_VOLUME_DIRECT_BLOCK.to_string(), + _ => { + return Err(anyhow!( + "unknown directly assigned volume type: {}", + value.volume_type + )) + } + }; + + Ok(KataVirtualVolume { + volume_type, + source: value.device.clone(), + fs_type: value.fs_type.clone(), + options: value.options.clone(), + direct_volume: Some(DirectAssignedVolume { + metadata: value.metadata.clone(), + }), + ..Default::default() + }) + } +} + +impl TryFrom<&NydusExtraOptions> for KataVirtualVolume { + type Error = Error; + + fn try_from(value: &NydusExtraOptions) -> std::result::Result { + let fs_type = match value.fs_version.as_str() { + "v6" => "rafsv6".to_string(), + "rafsv6" => "rafsv6".to_string(), + "v5" => "rafsv5".to_string(), + "rafsv5" => "rafsv5".to_string(), + _ => return Err(anyhow!("unknown RAFS version: {}", value.fs_version)), + }; + + Ok(KataVirtualVolume { + volume_type: KATA_VIRTUAL_VOLUME_IMAGE_NYDUS_FS.to_string(), + source: value.source.clone(), + fs_type, + options: vec![], + nydus_image: Some(NydusImageVolume { + config: value.config.clone(), + snapshot_dir: value.snapshot_dir.clone(), + }), + ..Default::default() + }) + } +} + /// Join user provided volume path with kata direct-volume root path. /// /// The `volume_path` is base64-encoded and then safely joined to the `prefix` @@ -235,8 +327,8 @@ pub fn join_path(prefix: &str, volume_path: &str) -> Result { /// get DirectVolume mountInfo from mountinfo.json. pub fn get_volume_mount_info(volume_path: &str) -> Result { - let mount_info_file_path = - join_path(KATA_DIRECT_VOLUME_ROOT_PATH, volume_path)?.join(KATA_MOUNT_INFO_FILE_NAME); + let volume_path = join_path(KATA_DIRECT_VOLUME_ROOT_PATH, volume_path)?; + let mount_info_file_path = volume_path.join(KATA_MOUNT_INFO_FILE_NAME); let mount_info_file = fs::read_to_string(mount_info_file_path)?; let mount_info: DirectVolumeMountInfo = serde_json::from_str(&mount_info_file)?; @@ -263,44 +355,6 @@ pub fn is_kata_host_dir_volume(ty: &str) -> bool { ty == KATA_HOST_DIR_VOLUME_TYPE } -/// Nydus extra options -#[derive(Debug, serde::Deserialize)] -pub struct NydusExtraOptions { - /// source path - pub source: String, - /// nydus config - pub config: String, - /// snapshotter directory - #[serde(rename(deserialize = "snapshotdir"))] - pub snapshot_dir: String, - /// fs version - pub fs_version: String, -} - -impl NydusExtraOptions { - /// Create Nydus extra options - pub fn new(mount: &Mount) -> Result { - let options: Vec<&str> = mount - .options - .iter() - .filter(|x| x.starts_with("extraoption=")) - .map(|x| x.as_ref()) - .collect(); - - if options.len() != 1 { - return Err(anyhow!( - "get_nydus_extra_options: Invalid nydus options: {:?}", - &mount.options - )); - } - let config_raw_data = options[0].trim_start_matches("extraoption="); - let extra_options_buf = - base64::decode(config_raw_data).context("decode the nydus's base64 extraoption")?; - - serde_json::from_slice(&extra_options_buf).context("deserialize nydus's extraoption") - } -} - /// sandbox bindmount format: /path/to/dir, or /path/to/dir:ro[:rw] /// the real path is without suffix ":ro" or ":rw". pub fn split_bind_mounts(bindmount: &str) -> (&str, &str) { @@ -416,4 +470,56 @@ mod tests { assert_eq!(volume.nydus_image, volume2.nydus_image); assert_eq!(volume.direct_volume, volume2.direct_volume); } + + #[test] + fn test_try_from_direct_volume() { + let mut metadata = HashMap::new(); + metadata.insert("mode".to_string(), "rw".to_string()); + let mut direct = DirectVolumeMountInfo { + volume_type: "unknown".to_string(), + device: "/dev/vda".to_string(), + fs_type: "ext4".to_string(), + metadata, + options: vec!["ro".to_string()], + }; + KataVirtualVolume::try_from(&direct).unwrap_err(); + + direct.volume_type = "block".to_string(); + let volume = KataVirtualVolume::try_from(&direct).unwrap(); + assert_eq!( + volume.volume_type.as_str(), + KATA_VIRTUAL_VOLUME_DIRECT_BLOCK + ); + assert_eq!(volume.source, direct.device); + assert_eq!(volume.fs_type, direct.fs_type); + assert_eq!( + volume.direct_volume.as_ref().unwrap().metadata, + direct.metadata + ); + assert_eq!(volume.options, direct.options); + } + + #[test] + fn test_try_from_nydus_extra_options() { + let mut nydus = NydusExtraOptions { + source: "/test/nydus".to_string(), + config: "test".to_string(), + snapshot_dir: "/var/lib/nydus".to_string(), + fs_version: "rafsvx".to_string(), + }; + KataVirtualVolume::try_from(&nydus).unwrap_err(); + + nydus.fs_version = "v6".to_string(); + let volume = KataVirtualVolume::try_from(&nydus).unwrap(); + assert_eq!( + volume.volume_type.as_str(), + KATA_VIRTUAL_VOLUME_IMAGE_NYDUS_FS + ); + assert_eq!(volume.nydus_image.as_ref().unwrap().config, nydus.config); + assert_eq!( + volume.nydus_image.as_ref().unwrap().snapshot_dir, + nydus.snapshot_dir + ); + assert_eq!(volume.fs_type.as_str(), "rafsv6") + } } From b875e3932329146503f37a56a0d626b0094f99d9 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Sun, 20 Aug 2023 16:54:31 +0800 Subject: [PATCH 3/4] kata-types: validate KataVirtualVolume object Implement method validate() for KataVirtualVolume to validate message format. Signed-off-by: Jiang Liu --- src/libs/kata-types/src/mount.rs | 82 ++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/libs/kata-types/src/mount.rs b/src/libs/kata-types/src/mount.rs index 7a60646f3a..da57208842 100644 --- a/src/libs/kata-types/src/mount.rs +++ b/src/libs/kata-types/src/mount.rs @@ -258,6 +258,88 @@ impl KataVirtualVolume { ..Default::default() } } + + /// Validate virtual volume object. + pub fn validate(&self) -> Result<()> { + match self.volume_type.as_str() { + KATA_VIRTUAL_VOLUME_DIRECT_BLOCK => { + if self.source.is_empty() { + return Err(anyhow!( + "missing source device for directly assigned block volume" + )); + } else if self.fs_type.is_empty() { + return Err(anyhow!( + "missing filesystem for directly assigned block volume" + )); + } + } + KATA_VIRTUAL_VOLUME_IMAGE_RAW_BLOCK | KATA_VIRTUAL_VOLUME_LAYER_RAW_BLOCK => { + if self.source.is_empty() { + return Err(anyhow!("missing source device for raw block volume")); + } else if self.fs_type.is_empty() { + return Err(anyhow!("missing filesystem for raw block volume")); + } + } + KATA_VIRTUAL_VOLUME_IMAGE_NYDUS_BLOCK | KATA_VIRTUAL_VOLUME_LAYER_NYDUS_BLOCK => { + if self.source.is_empty() { + return Err(anyhow!("missing meta blob for nydus block volume")); + } else if self.fs_type.as_str() != "rafsv6" { + return Err(anyhow!("invalid filesystem for nydus block volume")); + } + match self.nydus_image.as_ref() { + None => { + return Err(anyhow!( + "missing nydus configuration info for nydus block volume" + )) + } + Some(nydus) => { + if nydus.config.is_empty() { + return Err(anyhow!( + "missing configuration info for nydus block volume" + )); + } else if nydus.snapshot_dir.is_empty() { + return Err(anyhow!( + "missing snapshot directory for nydus block volume" + )); + } + } + } + } + KATA_VIRTUAL_VOLUME_IMAGE_NYDUS_FS | KATA_VIRTUAL_VOLUME_LAYER_NYDUS_FS => { + if self.source.is_empty() { + return Err(anyhow!("missing meta blob for nydus fs volume")); + } else if self.fs_type.as_str() != "rafsv6" && self.fs_type.as_str() != "rafsv5" { + return Err(anyhow!("invalid filesystem for nydus fs volume")); + } + match self.nydus_image.as_ref() { + None => { + return Err(anyhow!( + "missing nydus configuration info for nydus block volume" + )) + } + Some(nydus) => { + if nydus.config.is_empty() { + return Err(anyhow!( + "missing configuration info for nydus block volume" + )); + } else if nydus.snapshot_dir.is_empty() { + return Err(anyhow!( + "missing snapshot directory for nydus block volume" + )); + } + } + } + } + KATA_VIRTUAL_VOLUME_IMAGE_GUEST_PULL => { + if self.source.is_empty() { + return Err(anyhow!("missing image reference for guest pulling volume")); + } + } + _ => {} + } + + Ok(()) + } } impl TryFrom<&DirectVolumeMountInfo> for KataVirtualVolume { From 4aee3eade0ab8697a3e28eaf3de6222f471754ab Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Sun, 20 Aug 2023 23:08:26 +0800 Subject: [PATCH 4/4] kata-types: implement serde methods for KataVirtualVolume Implement serilization/deserialization methods for KataVirtualVolume. Signed-off-by: Jiang Liu --- src/libs/kata-types/src/mount.rs | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/libs/kata-types/src/mount.rs b/src/libs/kata-types/src/mount.rs index da57208842..1c0e69d3ec 100644 --- a/src/libs/kata-types/src/mount.rs +++ b/src/libs/kata-types/src/mount.rs @@ -340,6 +340,32 @@ impl KataVirtualVolume { Ok(()) } + + /// Serialize the virtual volume object to json. + pub fn to_json(&self) -> Result { + Ok(serde_json::to_string(self)?) + } + + /// Deserialize a virtual volume object from json string. + pub fn from_json(value: &str) -> Result { + let volume: KataVirtualVolume = serde_json::from_str(value)?; + volume.validate()?; + Ok(volume) + } + + /// Serialize the virtual volume object to json and encode the string with base64. + pub fn to_base64(&self) -> Result { + let json = self.to_json()?; + Ok(base64::encode(json)) + } + + /// Decode and deserialize a virtual volume object from base64 encoded json string. + pub fn from_base64(value: &str) -> Result { + let json = base64::decode(value)?; + let volume: KataVirtualVolume = serde_json::from_slice(&json)?; + volume.validate()?; + Ok(volume) + } } impl TryFrom<&DirectVolumeMountInfo> for KataVirtualVolume { @@ -553,6 +579,29 @@ mod tests { assert_eq!(volume.direct_volume, volume2.direct_volume); } + #[test] + fn test_kata_virtual_volume_serde() { + let mut volume = KataVirtualVolume::new(KATA_VIRTUAL_VOLUME_DIRECT_BLOCK.to_string()); + volume.source = "/tmp".to_string(); + volume.fs_type = "ext4".to_string(); + volume.options = vec!["rw".to_string()]; + volume.nydus_image = Some(NydusImageVolume { + config: "test".to_string(), + snapshot_dir: "/var/lib/nydus.dir".to_string(), + }); + let mut metadata = HashMap::new(); + metadata.insert("mode".to_string(), "rw".to_string()); + volume.direct_volume = Some(DirectAssignedVolume { metadata }); + + let value = volume.to_base64().unwrap(); + let volume2: KataVirtualVolume = KataVirtualVolume::from_base64(value.as_str()).unwrap(); + assert_eq!(volume.volume_type, volume2.volume_type); + assert_eq!(volume.source, volume2.source); + assert_eq!(volume.fs_type, volume2.fs_type); + assert_eq!(volume.nydus_image, volume2.nydus_image); + assert_eq!(volume.direct_volume, volume2.direct_volume); + } + #[test] fn test_try_from_direct_volume() { let mut metadata = HashMap::new();