diff --git a/src/libs/kata-types/src/mount.rs b/src/libs/kata-types/src/mount.rs index 83bcca3ea8..1c0e69d3ec 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. @@ -34,6 +35,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 +84,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,47 +98,6 @@ 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 -pub fn join_path(prefix: &str, volume_path: &str) -> Result { - if volume_path.is_empty() { - return Err(anyhow!("volume path must not be empty")); - } - let b64_encoded_path = base64::encode(volume_path.as_bytes()); - - Ok(safe_path::scoped_join(prefix, b64_encoded_path)?) -} - -/// 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 mount_info_file = fs::read_to_string(mount_info_file_path)?; - let mount_info: DirectVolumeMountInfo = serde_json::from_str(&mount_info_file)?; - - Ok(mount_info) -} - -/// Check whether a mount type is a marker for Kata specific volume. -pub fn is_kata_special_volume(ty: &str) -> bool { - ty.len() > KATA_VOLUME_TYPE_PREFIX.len() && ty.starts_with(KATA_VOLUME_TYPE_PREFIX) -} - -/// Check whether a mount type is a marker for Kata guest mount volume. -pub fn is_kata_guest_mount_volume(ty: &str) -> bool { - ty.len() > KATA_GUEST_MOUNT_PREFIX.len() && ty.starts_with(KATA_GUEST_MOUNT_PREFIX) -} - -/// Check whether a mount type is a marker for Kata ephemeral volume. -pub fn is_kata_ephemeral_volume(ty: &str) -> bool { - ty == KATA_EPHEMERAL_VOLUME_TYPE -} - -/// Check whether a mount type is a marker for Kata hostdir volume. -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 { @@ -159,6 +136,333 @@ impl NydusExtraOptions { } } +/// 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() + } + } + + /// 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(()) + } + + /// 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 { + 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` +pub fn join_path(prefix: &str, volume_path: &str) -> Result { + if volume_path.is_empty() { + return Err(anyhow!("volume path must not be empty")); + } + let b64_encoded_path = base64::encode(volume_path.as_bytes()); + + Ok(safe_path::scoped_join(prefix, b64_encoded_path)?) +} + +/// get DirectVolume mountInfo from mountinfo.json. +pub fn get_volume_mount_info(volume_path: &str) -> Result { + 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)?; + + Ok(mount_info) +} + +/// Check whether a mount type is a marker for Kata specific volume. +pub fn is_kata_special_volume(ty: &str) -> bool { + ty.len() > KATA_VOLUME_TYPE_PREFIX.len() && ty.starts_with(KATA_VOLUME_TYPE_PREFIX) +} + +/// Check whether a mount type is a marker for Kata guest mount volume. +pub fn is_kata_guest_mount_volume(ty: &str) -> bool { + ty.len() > KATA_GUEST_MOUNT_PREFIX.len() && ty.starts_with(KATA_GUEST_MOUNT_PREFIX) +} + +/// Check whether a mount type is a marker for Kata ephemeral volume. +pub fn is_kata_ephemeral_volume(ty: &str) -> bool { + ty == KATA_EPHEMERAL_VOLUME_TYPE +} + +/// Check whether a mount type is a marker for Kata hostdir volume. +pub fn is_kata_host_dir_volume(ty: &str) -> bool { + ty == KATA_HOST_DIR_VOLUME_TYPE +} + /// 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) { @@ -242,4 +546,111 @@ 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); + } + + #[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(); + 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") + } }