diff --git a/src/agent/src/mount.rs b/src/agent/src/mount.rs index ec7cf7dff1..f2a4f92eff 100644 --- a/src/agent/src/mount.rs +++ b/src/agent/src/mount.rs @@ -9,7 +9,7 @@ use std::fs::{self, File}; use std::io::{BufRead, BufReader}; use std::ops::Deref; use std::path::Path; - +use std::process::Command; use anyhow::{anyhow, Context, Result}; use kata_sys_util::mount::{get_linux_mount_info, parse_mount_options}; use nix::mount::MsFlags; @@ -63,6 +63,52 @@ lazy_static! { ]; } +#[instrument] +pub async fn resize_file_system(mountpoint: &String, logger: &Logger) -> Result<()> { + let logger = logger.new(o!("subsystem" => "resize_file_system")); + match get_linux_mount_info(mountpoint) { + Ok(info) => { + info!( + logger, + "Resizing {} file system on device {:?}", + info.fs_type, + info.device + ); + + let status = match info.fs_type.as_str() { + "ext2" | "ext3" | "ext4" => { + Command::new("resize2fs") + .arg(&info.device) + .status() + .context("Failed to execute resize2fs")? + } + "xfs" => { + Command::new("xfs_growfs") + .arg(&info.path) + .status() + .context("Failed to execute xfs_growfs")? + } + other => { + return Err(anyhow!( + "Unsupported filesystem type: {} for device {}", + other, + info.device + )); + } + }; + + if !status.success() { + return Err(anyhow!( + "Filesystem resize failed with exit code: {:?}", + status.code() + )); + } + + Ok(()) + } + Err(e) => Err(anyhow!("Error getting mount info: {:?}", e)), + } +} #[instrument] pub fn baremount( source: &Path, diff --git a/src/agent/src/pci.rs b/src/agent/src/pci.rs index f9ca877137..f16cdaf8f9 100644 --- a/src/agent/src/pci.rs +++ b/src/agent/src/pci.rs @@ -7,7 +7,7 @@ use std::fmt; use std::ops::Deref; use std::str::FromStr; -use anyhow::anyhow; +use anyhow::{anyhow,Context}; // The PCI spec reserves 5 bits (0..31) for slot number (a.k.a. device // number) @@ -18,11 +18,21 @@ const SLOT_MAX: u8 = (1 << SLOT_BITS) - 1; const FUNCTION_BITS: u8 = 3; const FUNCTION_MAX: u8 = (1 << FUNCTION_BITS) - 1; +const PCI_RESCAN_FILE: &str = "/sys/bus/pci/rescan"; +const PCI_RESCAN_SIGNAL: &str = "1"; // Represents a PCI function's slot (a.k.a. device) and function // numbers, giving its location on a single logical bus #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct SlotFn(u8); - +pub async fn rescan_pci_meta() -> anyhow::Result<()> { + tokio::fs::write(PCI_RESCAN_FILE, PCI_RESCAN_SIGNAL) + .await + .context(format!( + "PCI rescan failed: writing '{}' to {}", + PCI_RESCAN_SIGNAL, PCI_RESCAN_FILE + ))?; + Ok(()) +} impl SlotFn { pub fn new(ss: T, f: U) -> anyhow::Result where diff --git a/src/agent/src/rpc.rs b/src/agent/src/rpc.rs index 92d5a316ce..149c6f19c7 100644 --- a/src/agent/src/rpc.rs +++ b/src/agent/src/rpc.rs @@ -66,7 +66,7 @@ use crate::device::network_device_handler::wait_for_pci_net_interface; use crate::device::{add_devices, handle_cdi_devices, update_env_pci}; use crate::features::get_build_features; use crate::metrics::get_metrics; -use crate::mount::baremount; +use crate::mount::{baremount, resize_file_system}; use crate::namespace::{NSTYPEIPC, NSTYPEPID, NSTYPEUTS}; use crate::network::setup_guest_dns; use crate::passfd_io; @@ -825,7 +825,17 @@ impl agent_ttrpc::AgentService for AgentService { Ok(Empty::new()) } - + async fn resize_volume( + &self, + ctx: &TtrpcContext, + req: protocols::agent::ResizeVolumeRequest, + ) -> ttrpc::Result { + trace_rpc_call!(ctx, "resize_volume", req); + is_allowed(&req).await?; + pci::rescan_pci_meta().await.map_ttrpc_err(same)?; + resize_file_system(&req.volume_guest_path, &sl()).await.map_ttrpc_err(same)?; + Ok(Empty::new()) + } async fn stats_container( &self, ctx: &TtrpcContext, diff --git a/src/runtime/pkg/govmm/qemu/qmp.go b/src/runtime/pkg/govmm/qemu/qmp.go index 4eafb66767..2b84ce6013 100644 --- a/src/runtime/pkg/govmm/qemu/qmp.go +++ b/src/runtime/pkg/govmm/qemu/qmp.go @@ -826,10 +826,18 @@ func (q *QMP) ExecuteBlockdevAddWithCache(ctx context.Context, blockDevice *Bloc "direct": direct, "no-flush": noFlush, } - return q.executeCommand(ctx, "blockdev-add", blockdevArgs, nil) } +func (q *QMP) ExecuteBlockdevResize(ctx context.Context, blockDeviceID string, size uint64) error { + blockdevArgs := map[string]interface{}{ + "node-name": blockDeviceID, + "size": size, + } + + return q.executeCommand(ctx, "block_resize", blockdevArgs, nil) +} + // ExecuteBlockdevAddWithDriverCache has three one parameter driver // than ExecuteBlockdevAddWithCache. // Parameter driver can set the driver of block device. diff --git a/src/runtime/virtcontainers/clh.go b/src/runtime/virtcontainers/clh.go index 8714794d98..73ee99c8f9 100644 --- a/src/runtime/virtcontainers/clh.go +++ b/src/runtime/virtcontainers/clh.go @@ -1144,7 +1144,9 @@ func (clh *cloudHypervisor) ResizeVCPUs(ctx context.Context, reqVCPUs uint32) (c return currentVCPUs, newVCPUs, nil } - +func (clh *cloudHypervisor) ResizeBlock(ctx context.Context, deviceID string, size uint64) error { + return nil +} func (clh *cloudHypervisor) Cleanup(ctx context.Context) error { clh.Logger().WithField("function", "Cleanup").Info("Cleanup") return nil diff --git a/src/runtime/virtcontainers/fc.go b/src/runtime/virtcontainers/fc.go index 3442edadbc..a11bd13d86 100644 --- a/src/runtime/virtcontainers/fc.go +++ b/src/runtime/virtcontainers/fc.go @@ -1183,6 +1183,9 @@ func (fc *firecracker) ResizeMemory(ctx context.Context, reqMemMB uint32, memory func (fc *firecracker) ResizeVCPUs(ctx context.Context, reqVCPUs uint32) (currentVCPUs uint32, newVCPUs uint32, err error) { return 0, 0, nil } +func (fc *firecracker) ResizeBlock(ctx context.Context, deviceID string, size uint64) error { + return nil +} // This is used to apply cgroup information on the host. // diff --git a/src/runtime/virtcontainers/hypervisor.go b/src/runtime/virtcontainers/hypervisor.go index 22423ab122..70e951250d 100644 --- a/src/runtime/virtcontainers/hypervisor.go +++ b/src/runtime/virtcontainers/hypervisor.go @@ -1124,6 +1124,7 @@ type Hypervisor interface { HotplugRemoveDevice(ctx context.Context, devInfo interface{}, devType DeviceType) (interface{}, error) ResizeMemory(ctx context.Context, memMB uint32, memoryBlockSizeMB uint32, probe bool) (uint32, MemoryDevice, error) ResizeVCPUs(ctx context.Context, vcpus uint32) (uint32, uint32, error) + ResizeBlock(ctx context.Context, deviceID string, size uint64) error GetTotalMemoryMB(ctx context.Context) uint32 GetVMConsole(ctx context.Context, sandboxID string) (string, string, error) Disconnect(ctx context.Context) diff --git a/src/runtime/virtcontainers/mock_hypervisor.go b/src/runtime/virtcontainers/mock_hypervisor.go index 7d6da561fa..4ee717ffc1 100644 --- a/src/runtime/virtcontainers/mock_hypervisor.go +++ b/src/runtime/virtcontainers/mock_hypervisor.go @@ -104,7 +104,9 @@ func (m *mockHypervisor) ResizeMemory(ctx context.Context, memMB uint32, memoryS func (m *mockHypervisor) ResizeVCPUs(ctx context.Context, cpus uint32) (uint32, uint32, error) { return 0, 0, nil } - +func (m *mockHypervisor) ResizeBlock(ctx context.Context, deviceID string, size uint64) error { + return nil +} func (m *mockHypervisor) GetTotalMemoryMB(ctx context.Context) uint32 { return m.config.MemorySize } diff --git a/src/runtime/virtcontainers/qemu.go b/src/runtime/virtcontainers/qemu.go index 0de579c870..847e0e84db 100644 --- a/src/runtime/virtcontainers/qemu.go +++ b/src/runtime/virtcontainers/qemu.go @@ -2573,6 +2573,10 @@ func (q *qemu) ResizeMemory(ctx context.Context, reqMemMB uint32, memoryBlockSiz return currentMemory, addMemDevice, nil } +func (q *qemu) ResizeBlock(ctx context.Context, deviceID string, size uint64) error { + return q.qmpMonitorCh.qmp.ExecuteBlockdevResize(ctx, deviceID, size) +} + // genericAppendBridges appends to devices the given bridges // nolint: unused, deadcode func genericAppendBridges(devices []govmmQemu.Device, bridges []types.Bridge, machineType string) []govmmQemu.Device { diff --git a/src/runtime/virtcontainers/remote.go b/src/runtime/virtcontainers/remote.go index 0e583ed9ee..44c8788f75 100644 --- a/src/runtime/virtcontainers/remote.go +++ b/src/runtime/virtcontainers/remote.go @@ -228,7 +228,9 @@ func (rh *remoteHypervisor) GetTotalMemoryMB(ctx context.Context) uint32 { func (rh *remoteHypervisor) ResizeVCPUs(ctx context.Context, vcpus uint32) (uint32, uint32, error) { return vcpus, vcpus, nil } - +func (rh *remoteHypervisor) ResizeBlock(ctx context.Context, deviceID string, size uint64) error { + return nil +} func (rh *remoteHypervisor) GetVMConsole(ctx context.Context, sandboxID string) (string, string, error) { return "", "", notImplemented("GetVMConsole") } diff --git a/src/runtime/virtcontainers/sandbox.go b/src/runtime/virtcontainers/sandbox.go index 01a466bb47..cdad83b214 100644 --- a/src/runtime/virtcontainers/sandbox.go +++ b/src/runtime/virtcontainers/sandbox.go @@ -2665,7 +2665,7 @@ func (s *Sandbox) SetPolicy(ctx context.Context, policy string) error { // GuestVolumeStats return the filesystem stat of a given volume in the guest. func (s *Sandbox) GuestVolumeStats(ctx context.Context, volumePath string) ([]byte, error) { - guestMountPath, err := s.guestMountPath(volumePath) + _, guestMountPath, err := s.getMountDetails(volumePath) if err != nil { return nil, err } @@ -2674,30 +2674,47 @@ func (s *Sandbox) GuestVolumeStats(ctx context.Context, volumePath string) ([]by // ResizeGuestVolume resizes a volume in the guest. func (s *Sandbox) ResizeGuestVolume(ctx context.Context, volumePath string, size uint64) error { - // TODO: https://github.com/kata-containers/kata-containers/issues/3694. - guestMountPath, err := s.guestMountPath(volumePath) + + id, guestMountPath, err := s.getMountDetails(volumePath) + if err != nil { return err } + device := s.devManager.GetDeviceByID(id) + if device == nil { + s.Logger().WithField("device", id).Error("failed to find device by id") + return fmt.Errorf("failed to find device by id (id=%s)", id) + } + + d, ok := device.GetDeviceInfo().(*config.BlockDrive) + if !ok || d == nil { + s.Logger().WithField("device", device).Error("device is not a block drive or is nil") + return fmt.Errorf("device is not a block drive or is nil") + } + if err := s.hypervisor.ResizeBlock(ctx, d.ID, size); err != nil { + s.Logger().WithError(err).Error("Failed to resize block device in hypervisor") + return err + } + return s.agent.resizeGuestVolume(ctx, guestMountPath, size) } -func (s *Sandbox) guestMountPath(volumePath string) (string, error) { +func (s *Sandbox) getMountDetails(volumePath string) (string, string, error) { // verify the device even exists if _, err := os.Stat(volumePath); err != nil { s.Logger().WithError(err).WithField("volume", volumePath).Error("Cannot get stats for volume that doesn't exist") - return "", err + return "", "", err } // verify that we have a mount in this sandbox who's source maps to this for _, c := range s.containers { for _, m := range c.mounts { if volumePath == m.Source { - return m.GuestDeviceMount, nil + return m.BlockDeviceID, m.GuestDeviceMount, nil } } } - return "", fmt.Errorf("mount %s not found in sandbox", volumePath) + return "", "", fmt.Errorf("mount %s not found in sandbox", volumePath) } // getSandboxCPUSet returns the union of each of the sandbox's containers' CPU sets' diff --git a/src/runtime/virtcontainers/stratovirt.go b/src/runtime/virtcontainers/stratovirt.go index 9bfab6bf44..0c64862037 100644 --- a/src/runtime/virtcontainers/stratovirt.go +++ b/src/runtime/virtcontainers/stratovirt.go @@ -1169,7 +1169,9 @@ func (s *stratovirt) ResizeMemory(ctx context.Context, reqMemMB uint32, memoryBl func (s *stratovirt) ResizeVCPUs(ctx context.Context, reqVCPUs uint32) (currentVCPUs uint32, newVCPUs uint32, err error) { return 0, 0, nil } - +func (s *stratovirt) ResizeBlock(ctx context.Context, deviceID string, size uint64) error { + return nil +} func (s *stratovirt) GetVMConsole(ctx context.Context, id string) (string, string, error) { span, _ := katatrace.Trace(ctx, s.Logger(), "GetVMConsole", stratovirtTracingTags, map[string]string{"sandbox_id": s.id}) defer span.End() diff --git a/src/tools/genpolicy/rules.rego b/src/tools/genpolicy/rules.rego index b402b1c5ec..6c25e711f6 100644 --- a/src/tools/genpolicy/rules.rego +++ b/src/tools/genpolicy/rules.rego @@ -29,6 +29,7 @@ default ReadStreamRequest := false default RemoveContainerRequest := true default RemoveStaleVirtiofsShareMountsRequest := true default ReseedRandomDevRequest := false +default ResizeVolumeRequest := false default ResumeContainerRequest := false default SetGuestDateTimeRequest := false default SetPolicyRequest := false