diff --git a/src/agent/src/rpc.rs b/src/agent/src/rpc.rs index 5fce4fb4ee..765a3a6bf1 100644 --- a/src/agent/src/rpc.rs +++ b/src/agent/src/rpc.rs @@ -40,13 +40,11 @@ use rustjail::specconv::CreateOpts; use nix::errno::Errno; use nix::mount::MsFlags; -use nix::sys::stat; +use nix::sys::{stat, statfs}; use nix::unistd::{self, Pid}; use rustjail::cgroups::Manager; use rustjail::process::ProcessOperations; -use sysinfo::{DiskExt, System, SystemExt}; - use crate::device::{ add_devices, get_virtio_blk_pci_device_name, update_device_cgroup, update_env_pci, }; @@ -71,7 +69,6 @@ use tracing::instrument; use libc::{self, c_char, c_ushort, pid_t, winsize, TIOCSWINSZ}; use std::fs; -use std::os::unix::fs::MetadataExt; use std::os::unix::prelude::PermissionsExt; use std::process::{Command, Stdio}; use std::time::Duration; @@ -1452,20 +1449,12 @@ fn get_memory_info(block_size: bool, hotplug: bool) -> Result<(u64, bool)> { fn get_volume_capacity_stats(path: &str) -> Result { let mut usage = VolumeUsage::new(); - let s = System::new(); - for disk in s.disks() { - if let Some(v) = disk.name().to_str() { - if v.to_string().eq(path) { - usage.available = disk.available_space(); - usage.total = disk.total_space(); - usage.used = usage.total - usage.available; - usage.unit = VolumeUsage_Unit::BYTES; // bytes - break; - } - } else { - return Err(anyhow!(nix::Error::EINVAL)); - } - } + let stat = statfs::statfs(path)?; + let block_size = stat.block_size() as u64; + usage.total = stat.blocks() * block_size; + usage.available = stat.blocks_free() * block_size; + usage.used = usage.total - usage.available; + usage.unit = VolumeUsage_Unit::BYTES; Ok(usage) } @@ -1473,20 +1462,11 @@ fn get_volume_capacity_stats(path: &str) -> Result { fn get_volume_inode_stats(path: &str) -> Result { let mut usage = VolumeUsage::new(); - let s = System::new(); - for disk in s.disks() { - if let Some(v) = disk.name().to_str() { - if v.to_string().eq(path) { - let meta = fs::metadata(disk.mount_point())?; - let inode = meta.ino(); - usage.used = inode; - usage.unit = VolumeUsage_Unit::INODES; - break; - } - } else { - return Err(anyhow!(nix::Error::EINVAL)); - } - } + let stat = statfs::statfs(path)?; + usage.total = stat.files(); + usage.available = stat.files_free(); + usage.used = usage.total - usage.available; + usage.unit = VolumeUsage_Unit::INODES; Ok(usage) } @@ -1866,7 +1846,8 @@ fn load_kernel_module(module: &protocols::agent::KernelModule) -> Result<()> { #[cfg(test)] mod tests { use super::*; - use crate::protocols::agent_ttrpc::AgentService as _; + use crate::{protocols::agent_ttrpc::AgentService as _, skip_if_not_root}; + use nix::mount; use oci::{Hook, Hooks}; use ttrpc::{r#async::TtrpcContext, MessageHeader}; @@ -2197,4 +2178,66 @@ mod tests { } } } + + #[tokio::test] + async fn test_volume_capacity_stats() { + skip_if_not_root!(); + + // Verify error if path does not exist + assert!(get_volume_capacity_stats("/does-not-exist").is_err()); + + // Create a new tmpfs mount, and verify the initial values + let mount_dir = tempfile::tempdir().unwrap(); + mount::mount( + Some("tmpfs"), + mount_dir.path().to_str().unwrap(), + Some("tmpfs"), + mount::MsFlags::empty(), + None::<&str>, + ) + .unwrap(); + let mut stats = get_volume_capacity_stats(mount_dir.path().to_str().unwrap()).unwrap(); + assert_eq!(stats.used, 0); + assert_ne!(stats.available, 0); + let available = stats.available; + + // Verify that writing a file will result in increased utilization + fs::write(mount_dir.path().join("file.dat"), "foobar").unwrap(); + stats = get_volume_capacity_stats(mount_dir.path().to_str().unwrap()).unwrap(); + + assert_eq!(stats.used, 4 * 1024); + assert_eq!(stats.available, available - 4 * 1024); + } + + #[tokio::test] + async fn test_get_volume_inode_stats() { + skip_if_not_root!(); + + // Verify error if path does not exist + assert!(get_volume_inode_stats("/does-not-exist").is_err()); + + // Create a new tmpfs mount, and verify the initial values + let mount_dir = tempfile::tempdir().unwrap(); + mount::mount( + Some("tmpfs"), + mount_dir.path().to_str().unwrap(), + Some("tmpfs"), + mount::MsFlags::empty(), + None::<&str>, + ) + .unwrap(); + let mut stats = get_volume_inode_stats(mount_dir.path().to_str().unwrap()).unwrap(); + assert_eq!(stats.used, 1); + assert_ne!(stats.available, 0); + let available = stats.available; + + // Verify that creating a directory and writing a file will result in increased utilization + let dir = mount_dir.path().join("foobar"); + fs::create_dir_all(&dir).unwrap(); + fs::write(dir.as_path().join("file.dat"), "foobar").unwrap(); + stats = get_volume_inode_stats(mount_dir.path().to_str().unwrap()).unwrap(); + + assert_eq!(stats.used, 3); + assert_eq!(stats.available, available - 2); + } } diff --git a/src/runtime/cmd/kata-runtime/kata-volume.go b/src/runtime/cmd/kata-runtime/kata-volume.go index e08e9482fa..55274f7d80 100644 --- a/src/runtime/cmd/kata-runtime/kata-volume.go +++ b/src/runtime/cmd/kata-runtime/kata-volume.go @@ -7,10 +7,11 @@ package main import ( "encoding/json" + "fmt" "net/url" containerdshim "github.com/kata-containers/kata-containers/src/runtime/pkg/containerd-shim-v2" - "github.com/kata-containers/kata-containers/src/runtime/pkg/direct-volume" + volume "github.com/kata-containers/kata-containers/src/runtime/pkg/direct-volume" "github.com/kata-containers/kata-containers/src/runtime/pkg/utils/shimclient" "github.com/urfave/cli" @@ -89,12 +90,14 @@ var statsCommand = cli.Command{ Destination: &volumePath, }, }, - Action: func(c *cli.Context) (string, error) { + Action: func(c *cli.Context) error { stats, err := Stats(volumePath) if err != nil { - return "", cli.NewExitError(err.Error(), 1) + return cli.NewExitError(err.Error(), 1) } - return string(stats), nil + + fmt.Println(string(stats)) + return nil }, } @@ -127,8 +130,14 @@ func Stats(volumePath string) ([]byte, error) { if err != nil { return nil, err } - urlSafeDevicePath := url.PathEscape(volumePath) - body, err := shimclient.DoGet(sandboxId, defaultTimeout, containerdshim.DirectVolumeStatUrl+"/"+urlSafeDevicePath) + volumeMountInfo, err := volume.VolumeMountInfo(volumePath) + if err != nil { + return nil, err + } + + urlSafeDevicePath := url.PathEscape(volumeMountInfo.Device) + body, err := shimclient.DoGet(sandboxId, defaultTimeout, + fmt.Sprintf("%s?%s=%s", containerdshim.DirectVolumeStatUrl, containerdshim.DirectVolumePathKey, urlSafeDevicePath)) if err != nil { return nil, err } @@ -141,8 +150,13 @@ func Resize(volumePath string, size uint64) error { if err != nil { return err } + volumeMountInfo, err := volume.VolumeMountInfo(volumePath) + if err != nil { + return err + } + resizeReq := containerdshim.ResizeRequest{ - VolumePath: volumePath, + VolumePath: volumeMountInfo.Device, Size: size, } encoded, err := json.Marshal(resizeReq) diff --git a/src/runtime/pkg/containerd-shim-v2/shim_management.go b/src/runtime/pkg/containerd-shim-v2/shim_management.go index b5ad03eed2..e109222507 100644 --- a/src/runtime/pkg/containerd-shim-v2/shim_management.go +++ b/src/runtime/pkg/containerd-shim-v2/shim_management.go @@ -32,6 +32,8 @@ import ( ) const ( + DirectVolumePathKey = "path" + DirectVolumeStatUrl = "/direct-volume/stats" DirectVolumeResizeUrl = "/direct-volume/resize" ) @@ -139,7 +141,16 @@ func decodeAgentMetrics(body string) []*dto.MetricFamily { } func (s *service) serveVolumeStats(w http.ResponseWriter, r *http.Request) { - volumePath, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, DirectVolumeStatUrl)) + val := r.URL.Query().Get(DirectVolumePathKey) + if val == "" { + msg := fmt.Sprintf("Required parameter %s not found", DirectVolumePathKey) + shimMgtLog.Info(msg) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(msg)) + return + } + + volumePath, err := url.PathUnescape(val) if err != nil { shimMgtLog.WithError(err).Error("failed to unescape the volume stat url path") w.WriteHeader(http.StatusInternalServerError)