diff --git a/src/libs/Cargo.lock b/src/libs/Cargo.lock index fc7fc5da5f..f2ec072546 100644 --- a/src/libs/Cargo.lock +++ b/src/libs/Cargo.lock @@ -283,6 +283,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "kata-types" +version = "0.1.0" +dependencies = [ + "oci", + "serde", + "thiserror", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -414,6 +423,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "oci" +version = "0.1.0" +dependencies = [ + "libc", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "once_cell" version = "1.9.0" diff --git a/src/libs/Cargo.toml b/src/libs/Cargo.toml index 16eedb91f2..10887de3c1 100644 --- a/src/libs/Cargo.toml +++ b/src/libs/Cargo.toml @@ -1,7 +1,9 @@ [workspace] members = [ "logging", + "kata-types", "safe-path", "protocols", + "oci", ] resolver = "2" diff --git a/src/libs/README.md b/src/libs/README.md index 36f2f00d73..0593749a09 100644 --- a/src/libs/README.md +++ b/src/libs/README.md @@ -5,6 +5,7 @@ or published to [`crates.io`](https://crates.io/index.html). Currently it provides following library crates: | Library | Description | -|-|-|-| -| [logging](logging/) | Facilities to setup logging subsystem based slog. | +|-|-| +| [logging](logging/) | Facilities to setup logging subsystem based on slog. | +| [types](kata-types/) | Collection of constants and data types shared by multiple Kata Containers components. | | [safe-path](safe-path/) | Utilities to safely resolve filesystem paths. | diff --git a/src/libs/kata-types/Cargo.toml b/src/libs/kata-types/Cargo.toml new file mode 100644 index 0000000000..807791dc08 --- /dev/null +++ b/src/libs/kata-types/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "kata-types" +version = "0.1.0" +description = "Constants and data types shared by Kata Containers components" +keywords = ["kata", "container", "runtime"] +authors = ["The Kata Containers community "] +repository = "https://github.com/kata-containers/kata-containers.git" +homepage = "https://katacontainers.io/" +readme = "README.md" +license = "Apache-2.0" +edition = "2018" + +[dependencies] +serde = { version = "1.0.100", features = ["derive"] } +thiserror = "1.0" + +oci = { path = "../oci" } + +[dev-dependencies] diff --git a/src/libs/kata-types/README.md b/src/libs/kata-types/README.md new file mode 100644 index 0000000000..334c879e20 --- /dev/null +++ b/src/libs/kata-types/README.md @@ -0,0 +1,18 @@ +# kata-types + +This crate is a collection of constants and data types shared by multiple +[Kata Containers](https://github.com/kata-containers/kata-containers/) components. + +It defines constants and data types used by multiple Kata Containers components. Those constants +and data types may be defined by Kata Containers or by other projects/specifications, such as: +- [Containerd](https://github.com/containerd/containerd) +- [Kubelet](https://github.com/kubernetes/kubelet) + +## Support + +**Operating Systems**: +- Linux + +## License + +This code is licensed under [Apache-2.0](../../../LICENSE). diff --git a/src/libs/kata-types/src/annotations/cri_containerd.rs b/src/libs/kata-types/src/annotations/cri_containerd.rs new file mode 100644 index 0000000000..db6462a8c8 --- /dev/null +++ b/src/libs/kata-types/src/annotations/cri_containerd.rs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Alibaba Cloud +// Copyright (c) 2019 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 +// + +#![allow(missing_docs)] + +pub const CONTAINER_TYPE_LABEL_KEY: &str = "io.kubernetes.cri.container-type"; +pub const SANDBOX: &str = "sandbox"; +pub const CONTAINER: &str = "container"; + +pub const SANDBOX_ID_LABEL_KEY: &str = "io.kubernetes.cri.sandbox-id"; diff --git a/src/libs/kata-types/src/annotations/crio.rs b/src/libs/kata-types/src/annotations/crio.rs new file mode 100644 index 0000000000..c8b2311f84 --- /dev/null +++ b/src/libs/kata-types/src/annotations/crio.rs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Alibaba Cloud +// Copyright (c) 2019 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 +// + +#![allow(missing_docs)] + +pub const CONTAINER_TYPE_LABEL_KEY: &str = "io.kubernetes.cri.container-type"; +pub const SANDBOX: &str = "sandbox"; +pub const CONTAINER: &str = "container"; + +pub const SANDBOX_ID_LABEL_KEY: &str = "io.kubernetes.cri-o.SandboxID"; diff --git a/src/libs/kata-types/src/annotations/dockershim.rs b/src/libs/kata-types/src/annotations/dockershim.rs new file mode 100644 index 0000000000..1558983d9a --- /dev/null +++ b/src/libs/kata-types/src/annotations/dockershim.rs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Alibaba Cloud +// Copyright (c) 2019 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 +// + +#![allow(missing_docs)] + +pub const CONTAINER_TYPE_LABEL_KEY: &str = "io.kubernetes.docker.type"; +pub const SANDBOX: &str = "podsandbox"; +pub const CONTAINER: &str = "container"; + +pub const SANDBOX_ID_LABEL_KEY: &str = "io.kubernetes.sandbox.id"; diff --git a/src/libs/kata-types/src/annotations/mod.rs b/src/libs/kata-types/src/annotations/mod.rs new file mode 100644 index 0000000000..f1cd143204 --- /dev/null +++ b/src/libs/kata-types/src/annotations/mod.rs @@ -0,0 +1,14 @@ +// Copyright (c) 2019 Alibaba Cloud +// Copyright (c) 2019 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// CRI-containerd specific annotations. +pub mod cri_containerd; + +/// CRI-O specific annotations. +pub mod crio; + +/// Dockershim specific annotations. +pub mod dockershim; diff --git a/src/libs/kata-types/src/container.rs b/src/libs/kata-types/src/container.rs new file mode 100644 index 0000000000..aee94f95d3 --- /dev/null +++ b/src/libs/kata-types/src/container.rs @@ -0,0 +1,203 @@ +// Copyright (c) 2019-2021 Alibaba Cloud +// Copyright (c) 2019-2021 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +const CONTAINER: &str = "container"; +const SANDBOX: &str = "sandbox"; +const POD_CONTAINER: &str = "pod_container"; +const POD_SANDBOX: &str = "pod_sandbox"; +const POD_SANDBOX2: &str = "podsandbox"; + +const STATE_READY: &str = "ready"; +const STATE_RUNNING: &str = "running"; +const STATE_STOPPED: &str = "stopped"; +const STATE_PAUSED: &str = "paused"; + +/// Error codes for container related operations. +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// Invalid container type + #[error("Invalid container type {0}")] + InvalidContainerType(String), + /// Invalid container state + #[error("Invalid sandbox state {0}")] + InvalidState(String), + /// Invalid container state transition + #[error("Can not transit from {0} to {1}")] + InvalidStateTransition(State, State), +} + +/// Types of pod containers: container or sandbox. +#[derive(PartialEq, Debug, Clone)] +pub enum ContainerType { + /// A pod container. + PodContainer, + /// A pod sandbox. + PodSandbox, +} + +impl ContainerType { + /// Check whether it's a pod container. + pub fn is_pod_container(&self) -> bool { + matches!(self, ContainerType::PodContainer) + } + + /// Check whether it's a pod container. + pub fn is_pod_sandbox(&self) -> bool { + matches!(self, ContainerType::PodSandbox) + } +} + +impl Display for ContainerType { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + ContainerType::PodContainer => write!(f, "{}", POD_CONTAINER), + ContainerType::PodSandbox => write!(f, "{}", POD_SANDBOX), + } + } +} + +impl FromStr for ContainerType { + type Err = Error; + + fn from_str(value: &str) -> Result { + match value { + POD_CONTAINER | CONTAINER => Ok(ContainerType::PodContainer), + POD_SANDBOX | POD_SANDBOX2 | SANDBOX => Ok(ContainerType::PodSandbox), + _ => Err(Error::InvalidContainerType(value.to_owned())), + } + } +} + +/// Process states. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum State { + /// The container is ready to run. + Ready, + /// The container executed the user-specified program but has not exited + Running, + /// The container has exited + Stopped, + /// The container has been paused. + Paused, +} + +impl Display for State { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + State::Ready => write!(f, "{}", STATE_READY), + State::Running => write!(f, "{}", STATE_RUNNING), + State::Stopped => write!(f, "{}", STATE_STOPPED), + State::Paused => write!(f, "{}", STATE_PAUSED), + } + } +} + +impl FromStr for State { + type Err = Error; + + fn from_str(value: &str) -> Result { + match value { + STATE_READY => Ok(State::Ready), + STATE_RUNNING => Ok(State::Running), + STATE_STOPPED => Ok(State::Stopped), + STATE_PAUSED => Ok(State::Paused), + _ => Err(Error::InvalidState(value.to_owned())), + } + } +} + +impl State { + /// Check whether it's a valid state transition from self to the `new_state`. + pub fn check_transition(self, new_state: State) -> Result<(), Error> { + match self { + State::Ready if new_state == State::Running || new_state == State::Stopped => Ok(()), + State::Running if new_state == State::Stopped => Ok(()), + State::Stopped if new_state == State::Running => Ok(()), + State::Paused if new_state == State::Paused => Ok(()), + _ => Err(Error::InvalidStateTransition(self, new_state)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_container_type() { + assert!(ContainerType::PodContainer.is_pod_container()); + assert!(!ContainerType::PodContainer.is_pod_sandbox()); + + assert!(ContainerType::PodSandbox.is_pod_sandbox()); + assert!(!ContainerType::PodSandbox.is_pod_container()); + } + + #[test] + fn test_container_type_display() { + assert_eq!(format!("{}", ContainerType::PodContainer), POD_CONTAINER); + assert_eq!(format!("{}", ContainerType::PodSandbox), POD_SANDBOX); + } + + #[test] + fn test_container_type_from_str() { + assert_eq!( + ContainerType::from_str("pod_container").unwrap(), + ContainerType::PodContainer + ); + assert_eq!( + ContainerType::from_str("container").unwrap(), + ContainerType::PodContainer + ); + assert_eq!( + ContainerType::from_str("pod_sandbox").unwrap(), + ContainerType::PodSandbox + ); + assert_eq!( + ContainerType::from_str("podsandbox").unwrap(), + ContainerType::PodSandbox + ); + assert_eq!( + ContainerType::from_str("sandbox").unwrap(), + ContainerType::PodSandbox + ); + ContainerType::from_str("test").unwrap_err(); + } + + #[test] + fn test_valid() { + let mut state = State::from_str("invalid_state"); + assert!(state.is_err()); + + state = State::from_str("ready"); + assert!(state.is_ok()); + + state = State::from_str("running"); + assert!(state.is_ok()); + + state = State::from_str("stopped"); + assert!(state.is_ok()); + } + + #[test] + fn test_valid_transition() { + use State::*; + + assert!(Ready.check_transition(Ready).is_err()); + assert!(Ready.check_transition(Running).is_ok()); + assert!(Ready.check_transition(Stopped).is_ok()); + + assert!(Running.check_transition(Ready).is_err()); + assert!(Running.check_transition(Running).is_err()); + assert!(Running.check_transition(Stopped).is_ok()); + + assert!(Stopped.check_transition(Ready).is_err()); + assert!(Stopped.check_transition(Running).is_ok()); + assert!(Stopped.check_transition(Stopped).is_err()); + } +} diff --git a/src/libs/kata-types/src/k8s.rs b/src/libs/kata-types/src/k8s.rs new file mode 100644 index 0000000000..43f5ba839d --- /dev/null +++ b/src/libs/kata-types/src/k8s.rs @@ -0,0 +1,102 @@ +// Copyright (c) 2019-2021 Alibaba Cloud +// Copyright (c) 2019-2021 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::path::Path; + +use crate::annotations; +use crate::container::ContainerType; +use std::str::FromStr; + +// K8S_EMPTY_DIR is the k8s specific path for `empty-dir` volumes +const K8S_EMPTY_DIR: &str = "kubernetes.io~empty-dir"; + +/// Check whether the path is a K8S empty directory. +/// +/// For a K8S EmptyDir, Kubernetes mounts +/// "/var/lib/kubelet/pods//volumes/kubernetes.io~empty-dir/" +/// to "/". +pub fn is_empty_dir>(path: P) -> bool { + let path = path.as_ref(); + + if let Some(parent) = path.parent() { + if let Some(pname) = parent.file_name() { + if pname == K8S_EMPTY_DIR && parent.parent().is_some() { + return true; + } + } + } + + false +} + +/// Get K8S container type from OCI annotations. +pub fn container_type(spec: &oci::Spec) -> ContainerType { + // PodSandbox: "sandbox" (Containerd & CRI-O), "podsandbox" (dockershim) + // PodContainer: "container" (Containerd & CRI-O & dockershim) + for k in [ + annotations::crio::CONTAINER_TYPE_LABEL_KEY, + annotations::cri_containerd::CONTAINER_TYPE_LABEL_KEY, + annotations::dockershim::CONTAINER_TYPE_LABEL_KEY, + ] + .iter() + { + if let Some(v) = spec.annotations.get(k.to_owned()) { + if let Ok(t) = ContainerType::from_str(v) { + return t; + } + } + } + + ContainerType::PodSandbox +} + +/// Determine the k8s sandbox ID from OCI annotations. +/// +/// This function is expected to be called only when the container type is "PodContainer". +pub fn sandbox_id(spec: &oci::Spec) -> Result, String> { + if container_type(spec) != ContainerType::PodSandbox { + return Err("Not a sandbox container".to_string()); + } + for k in [ + annotations::crio::SANDBOX_ID_LABEL_KEY, + annotations::cri_containerd::SANDBOX_ID_LABEL_KEY, + annotations::dockershim::SANDBOX_ID_LABEL_KEY, + ] + .iter() + { + if let Some(id) = spec.annotations.get(k.to_owned()) { + return Ok(Some(id.to_string())); + } + } + + Ok(None) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_empty_dir() { + let empty_dir = "/volumes/kubernetes.io~empty-dir/shm"; + assert!(is_empty_dir(empty_dir)); + + let empty_dir = "/volumes/kubernetes.io~empty-dir//shm"; + assert!(is_empty_dir(empty_dir)); + + let empty_dir = "/volumes/kubernetes.io~empty-dir-test/shm"; + assert!(!is_empty_dir(empty_dir)); + + let empty_dir = "/volumes/kubernetes.io~empty-dir"; + assert!(!is_empty_dir(empty_dir)); + + let empty_dir = "kubernetes.io~empty-dir"; + assert!(!is_empty_dir(empty_dir)); + + let empty_dir = "/kubernetes.io~empty-dir/shm"; + assert!(is_empty_dir(empty_dir)); + } +} diff --git a/src/libs/kata-types/src/lib.rs b/src/libs/kata-types/src/lib.rs new file mode 100644 index 0000000000..5fde506150 --- /dev/null +++ b/src/libs/kata-types/src/lib.rs @@ -0,0 +1,20 @@ +// Copyright (c) 2021 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! Constants and Data Types shared by Kata Containers components. + +#[deny(missing_docs)] + +/// Constants and data types annotations. +pub mod annotations; + +/// Constants and data types related to container. +pub mod container; + +/// Constants and data types related to Kubernetes/kubelet. +pub mod k8s; + +/// Constants and data types related to mount point. +pub mod mount; diff --git a/src/libs/kata-types/src/mount.rs b/src/libs/kata-types/src/mount.rs new file mode 100644 index 0000000000..2ccc0feed2 --- /dev/null +++ b/src/libs/kata-types/src/mount.rs @@ -0,0 +1,88 @@ +// Copyright (c) 2019-2021 Alibaba Cloud +// Copyright (c) 2019-2021 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::path::PathBuf; + +/// Prefix to mark a volume as Kata special. +pub const KATA_VOLUME_TYPE_PREFIX: &str = "kata:"; + +/// The Mount should be ignored by the host and handled by the guest. +pub const KATA_GUEST_MOUNT_PREFIX: &str = "kata:guest-mount:"; + +/// KATA_EPHEMERAL_DEV_TYPE creates a tmpfs backed volume for sharing files between containers. +pub const KATA_EPHEMERAL_VOLUME_TYPE: &str = "kata:ephemeral"; + +/// KATA_HOST_DIR_TYPE use for host empty dir +pub const KATA_HOST_DIR_VOLUME_TYPE: &str = "kata:hostdir"; + +/// Information about a mount. +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +pub struct Mount { + /// A device name, but can also be a file or directory name for bind mounts or a dummy. + /// Path values for bind mounts are either absolute or relative to the bundle. A mount is a + /// bind mount if it has either bind or rbind in the options. + pub source: String, + /// Destination of mount point: path inside container. This value MUST be an absolute path. + pub destination: PathBuf, + /// The type of filesystem for the mountpoint. + pub fs_type: String, + /// Mount options for the mountpoint. + pub options: Vec, + /// Optional device id for the block device when: + /// - the source is a block device or a mountpoint for a block device + /// - block device direct assignment is enabled + pub device_id: Option, + /// Intermediate path to mount the source on host side and then passthrough to vm by shared fs. + pub host_shared_fs_path: Option, + /// Whether to mount the mountpoint in readonly mode + pub read_only: bool, +} + +impl Mount { + /// Get size of mount options. + pub fn option_size(&self) -> usize { + self.options.iter().map(|v| v.len() + 1).sum() + } +} + +/// 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 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_kata_special_volume() { + assert!(is_kata_special_volume("kata:guest-mount:nfs")); + assert!(!is_kata_special_volume("kata:")); + } + + #[test] + fn test_is_kata_guest_mount_volume() { + assert!(is_kata_guest_mount_volume("kata:guest-mount:nfs")); + assert!(!is_kata_guest_mount_volume("kata:guest-mount")); + assert!(!is_kata_guest_mount_volume("kata:guest-moun")); + assert!(!is_kata_guest_mount_volume("Kata:guest-mount:nfs")); + } +}