From 49cdb2c7579f7a655372c68b221835e6dddc8efc Mon Sep 17 00:00:00 2001 From: Ayush Srivastava Date: Sat, 24 Jan 2026 03:45:39 +0530 Subject: [PATCH 1/2] feat(hypervisors): add Cloud Hypervisor VMM support Signed-off-by: Ayush Srivastava --- .github/linters/urunc-dict.txt | 1 + docs/hypervisor-support.md | 50 +++++- .../hypervisors/cloud_hypervisor.go | 146 ++++++++++++++++++ pkg/unikontainers/hypervisors/vmm.go | 4 + tests/e2e/test_cases.go | 48 ++++++ 5 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 pkg/unikontainers/hypervisors/cloud_hypervisor.go diff --git a/.github/linters/urunc-dict.txt b/.github/linters/urunc-dict.txt index ab032d84..8d615469 100644 --- a/.github/linters/urunc-dict.txt +++ b/.github/linters/urunc-dict.txt @@ -349,6 +349,7 @@ DEFROUTE blockfile thinpool vcpus +cpus Virtiofs virtiofs virtiofsd diff --git a/docs/hypervisor-support.md b/docs/hypervisor-support.md index ac172303..1d0a30f4 100644 --- a/docs/hypervisor-support.md +++ b/docs/hypervisor-support.md @@ -19,9 +19,10 @@ somewhere in the `$PATH`. VMMs use hardware-assisted virtualization technologies in order to create a Virtual Machine (VM) where a guest OS will execute. It is one of the most widely used technology for providing strong isolation in multi-tenant -environments. For the time being `urunc` supports 3 types of such VMMs: 1) +environments. For the time being `urunc` supports 4 types of such VMMs: 1) [Qemu](https://www.qemu.org/), 2) -[Firecracker](https://firecracker-microvm.github.io/) and 3) [Solo5-hvt](https://github.com/Solo5/solo5). +[Firecracker](https://firecracker-microvm.github.io/), 3) +[Cloud Hypervisor](https://www.cloudhypervisor.org/) and 4) [Solo5-hvt](https://github.com/Solo5/solo5). ### Qemu @@ -139,6 +140,51 @@ An example unikernel: sudo nerdctl run --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/nginx-firecracker-unikraft-initrd:latest ``` +### Cloud Hypervisor + +[Cloud Hypervisor](https://www.cloudhypervisor.org/) is an open-source Virtual +Machine Monitor (VMM) that runs on top of the KVM hypervisor. It is part of the +rust-vmm project and works in a similar way to Firecracker. Cloud Hypervisor +provides a modern, secure, and efficient VMM with a focus on cloud workloads. +It supports virtio devices and offers fast boot times with minimal overhead. + +#### Installing Cloud Hypervisor + +Cloud Hypervisor can be installed by downloading a pre-built binary from the +[releases page](https://github.com/cloud-hypervisor/cloud-hypervisor/releases). + +```bash +ARCH="$(uname -m)" +VERSION="v43.0" +release_url="https://github.com/cloud-hypervisor/cloud-hypervisor/releases" +curl -L ${release_url}/download/${VERSION}/cloud-hypervisor-static-${ARCH} -o cloud-hypervisor +chmod +x cloud-hypervisor +sudo mv cloud-hypervisor /usr/local/bin/ +``` + +It is important to note that `urunc` expects to find the `cloud-hypervisor` +binary located in the `$PATH` and named `cloud-hypervisor`. + +#### Cloud Hypervisor and `urunc` + +In the case of [Cloud Hypervisor](https://www.cloudhypervisor.org/), `urunc` +makes use of its `virtio-net` device to provide network support for the +unikernel through a tap device. `urunc` can also leverage Cloud Hypervisor's +initramfs option to provide the unikernel with an initial RamFS. Cloud +Hypervisor supports virtio-block for storage and virtiofs for shared +filesystems between the host and guest. + +Supported unikernel frameworks with `urunc`: + +- [Unikraft](../unikernel-support#unikraft) +- [Linux](../unikernel-support#linux) + +An example unikernel: + +```bash +sudo nerdctl run --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/nginx-cloud-hypervisor-unikraft-initrd:latest +``` + ### Solo5-hvt [Solo5-hvt](https://github.com/Solo5/solo5) is a lightweight, high-performance diff --git a/pkg/unikontainers/hypervisors/cloud_hypervisor.go b/pkg/unikontainers/hypervisors/cloud_hypervisor.go new file mode 100644 index 00000000..4bd15d62 --- /dev/null +++ b/pkg/unikontainers/hypervisors/cloud_hypervisor.go @@ -0,0 +1,146 @@ +// Copyright (c) 2023-2025, Nubificus LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hypervisors + +import ( + "fmt" + "strings" + "syscall" + + "github.com/urunc-dev/urunc/pkg/unikontainers/types" +) + +const ( + CloudHypervisorVmm VmmType = "cloud-hypervisor" + CloudHypervisorBinary string = "cloud-hypervisor" +) + +type CloudHypervisor struct { + binaryPath string + binary string +} + +func (ch *CloudHypervisor) Stop(pid int) error { + return killProcess(pid) +} + +func (ch *CloudHypervisor) Ok() error { + return nil +} + +// UsesKVM returns true as Cloud Hypervisor is a KVM-based VMM +func (ch *CloudHypervisor) UsesKVM() bool { + return true +} + +// SupportsSharedfs returns true as Cloud Hypervisor supports virtiofs +func (ch *CloudHypervisor) SupportsSharedfs(fsType string) bool { + switch fsType { + case "virtiofs": + return true + default: + return false + } +} + +func (ch *CloudHypervisor) Path() string { + return ch.binaryPath +} + +func (ch *CloudHypervisor) Execve(args types.ExecArgs, ukernel types.Unikernel) error { + chMem := BytesToStringMB(args.MemSizeB) + + // Start building the command + cmdString := ch.binaryPath + + // Memory configuration + cmdString += fmt.Sprintf(" --memory size=%sM", chMem) + + // CPU configuration + if args.VCPUs > 0 { + cmdString += fmt.Sprintf(" --cpus boot=%d", args.VCPUs) + } + + // Kernel path + cmdString += " --kernel " + args.UnikernelPath + + // Console configuration - disable graphical output + cmdString += " --console off --serial tty" + + // Seccomp configuration + if args.Seccomp { + cmdString += " --seccomp true" + } else { + cmdString += " --seccomp false" + } + + // Network configuration + if args.Net.TapDev != "" { + netCli := ukernel.MonitorNetCli(args.Net.TapDev, args.Net.MAC) + if netCli == "" { + // Default network configuration for Cloud Hypervisor + cmdString += fmt.Sprintf(" --net tap=%s,mac=%s", args.Net.TapDev, args.Net.MAC) + } else { + cmdString += netCli + } + } + + // Block device configuration + blockArgs := ukernel.MonitorBlockCli() + for _, blockArg := range blockArgs { + if blockArg.ExactArgs != "" { + cmdString += blockArg.ExactArgs + } else if blockArg.Path != "" { + cmdString += fmt.Sprintf(" --disk path=%s", blockArg.Path) + if blockArg.ID != "" { + cmdString += fmt.Sprintf(",id=%s", blockArg.ID) + } + } + } + + // Initrd configuration + if args.InitrdPath != "" { + cmdString += " --initramfs " + args.InitrdPath + } + + // Check for extra initrd from unikernel monitor args + extraMonArgs := ukernel.MonitorCli() + if extraMonArgs.ExtraInitrd != "" && args.InitrdPath == "" { + cmdString += " --initramfs " + extraMonArgs.ExtraInitrd + } + + switch args.Sharedfs.Type { + case "virtiofs": + cmdString += fmt.Sprintf(" --fs tag=fs0,socket=/tmp/virtiofsd.sock") + default: + // No shared filesystem + } + + if args.VAccelType == "vsock" { + cmdString += fmt.Sprintf(" --vsock cid=%d,socket=%s/vaccel.sock", + args.VSockDevID, args.VSockDevPath) + } + + cmdString += extraMonArgs.OtherArgs + + exArgs := strings.Split(cmdString, " ") + + // Add the command line arguments for the kernel + exArgs = append(exArgs, "--cmdline", args.Command) + + vmmLog.WithField("cloud-hypervisor command", exArgs).Debug("Ready to execve cloud-hypervisor") + + return syscall.Exec(ch.Path(), exArgs, args.Environment) //nolint: gosec +} diff --git a/pkg/unikontainers/hypervisors/vmm.go b/pkg/unikontainers/hypervisors/vmm.go index bac4eb8c..25c906fe 100644 --- a/pkg/unikontainers/hypervisors/vmm.go +++ b/pkg/unikontainers/hypervisors/vmm.go @@ -52,6 +52,10 @@ var vmmFactories = map[VmmType]VMMFactory{ binary: FirecrackerBinary, createFunc: func(binary, binaryPath string) types.VMM { return &Firecracker{binary: binary, binaryPath: binaryPath} }, }, + CloudHypervisorVmm: { + binary: CloudHypervisorBinary, + createFunc: func(binary, binaryPath string) types.VMM { return &CloudHypervisor{binary: binary, binaryPath: binaryPath} }, + }, } func NewVMM(vmmType VmmType, monitors map[string]types.MonitorConfig) (vmm types.VMM, err error) { diff --git a/tests/e2e/test_cases.go b/tests/e2e/test_cases.go index 4af24797..958d5fb5 100644 --- a/tests/e2e/test_cases.go +++ b/tests/e2e/test_cases.go @@ -412,6 +412,54 @@ func nerdctlTestCases(kvmGroup ...int64) []containerTestArgs { ExpectOut: "", TestFunc: blockMountTest, }, + { + Image: "harbor.nbfc.io/nubificus/urunc/nginx-cloud-hypervisor-unikraft-initrd:latest", + Name: "CloudHypervisor-unikraft-ping-nginx", + Devmapper: false, + Seccomp: true, + UID: 0, + GID: 0, + Groups: []int64{}, + Memory: "", + Cli: "", + Volumes: []containerVolume{}, + StaticNet: false, + SideContainers: []string{}, + Skippable: true, // Skip until Cloud Hypervisor images are available + TestFunc: pingTest, + }, + { + Image: "harbor.nbfc.io/nubificus/urunc/nginx-cloud-hypervisor-unikraft-initrd:latest", + Name: "CloudHypervisor-unikraft-with-seccomp", + Devmapper: false, + Seccomp: true, + UID: 0, + GID: 0, + Groups: []int64{}, + Memory: "", + Cli: "", + Volumes: []containerVolume{}, + StaticNet: false, + SideContainers: []string{}, + Skippable: true, // Skip until Cloud Hypervisor images are available + TestFunc: seccompTest, + }, + { + Image: "harbor.nbfc.io/nubificus/urunc/nginx-cloud-hypervisor-linux-raw:latest", + Name: "CloudHypervisor-linux-nginx", + Devmapper: true, + Seccomp: true, + UID: 0, + GID: 0, + Groups: []int64{}, + Memory: "512M", + Cli: "", + Volumes: []containerVolume{}, + StaticNet: false, + SideContainers: []string{}, + Skippable: true, // Skip until Cloud Hypervisor images are available + TestFunc: pingTest, + }, } } From 3d69997ea726458b27d6bed29dfd144285d7eef6 Mon Sep 17 00:00:00 2001 From: Ayush Date: Tue, 27 Jan 2026 20:06:40 +0530 Subject: [PATCH 2/2] test(e2e): restore and standardize cloud-hypervisor tests Signed-off-by: Ayush --- .../hypervisors/cloud_hypervisor.go | 43 ++++++++++--------- pkg/unikontainers/hypervisors/vmm.go | 6 ++- pkg/unikontainers/ipc.go | 2 +- pkg/unikontainers/mount.go | 37 ++++++++++++---- pkg/unikontainers/urunc_config.go | 9 ++-- pkg/unikontainers/urunc_config_test.go | 5 ++- tests/e2e/common.go | 7 +++ tests/e2e/nerdctl.go | 5 ++- tests/e2e/test_cases.go | 22 +++++----- tests/e2e/test_functions.go | 23 +++++++++- tests/e2e/utils.go | 5 ++- 11 files changed, 110 insertions(+), 54 deletions(-) diff --git a/pkg/unikontainers/hypervisors/cloud_hypervisor.go b/pkg/unikontainers/hypervisors/cloud_hypervisor.go index 4bd15d62..da38dd37 100644 --- a/pkg/unikontainers/hypervisors/cloud_hypervisor.go +++ b/pkg/unikontainers/hypervisors/cloud_hypervisor.go @@ -48,7 +48,7 @@ func (ch *CloudHypervisor) UsesKVM() bool { // SupportsSharedfs returns true as Cloud Hypervisor supports virtiofs func (ch *CloudHypervisor) SupportsSharedfs(fsType string) bool { switch fsType { - case "virtiofs": + case "virtio", "virtiofs": return true default: return false @@ -63,27 +63,27 @@ func (ch *CloudHypervisor) Execve(args types.ExecArgs, ukernel types.Unikernel) chMem := BytesToStringMB(args.MemSizeB) // Start building the command - cmdString := ch.binaryPath + exArgs := []string{ch.binaryPath} // Memory configuration - cmdString += fmt.Sprintf(" --memory size=%sM", chMem) + exArgs = append(exArgs, "--memory", fmt.Sprintf("size=%sM,shared=on", chMem)) // CPU configuration if args.VCPUs > 0 { - cmdString += fmt.Sprintf(" --cpus boot=%d", args.VCPUs) + exArgs = append(exArgs, "--cpus", fmt.Sprintf("boot=%d", args.VCPUs)) } // Kernel path - cmdString += " --kernel " + args.UnikernelPath + exArgs = append(exArgs, "--kernel", args.UnikernelPath) // Console configuration - disable graphical output - cmdString += " --console off --serial tty" + exArgs = append(exArgs, "--console", "off", "--serial", "tty") // Seccomp configuration if args.Seccomp { - cmdString += " --seccomp true" + exArgs = append(exArgs, "--seccomp", "true") } else { - cmdString += " --seccomp false" + exArgs = append(exArgs, "--seccomp", "false") } // Network configuration @@ -91,9 +91,9 @@ func (ch *CloudHypervisor) Execve(args types.ExecArgs, ukernel types.Unikernel) netCli := ukernel.MonitorNetCli(args.Net.TapDev, args.Net.MAC) if netCli == "" { // Default network configuration for Cloud Hypervisor - cmdString += fmt.Sprintf(" --net tap=%s,mac=%s", args.Net.TapDev, args.Net.MAC) + exArgs = append(exArgs, "--net", fmt.Sprintf("tap=%s,mac=%s", args.Net.TapDev, args.Net.MAC)) } else { - cmdString += netCli + exArgs = append(exArgs, strings.Split(strings.TrimSpace(netCli), " ")...) } } @@ -101,41 +101,42 @@ func (ch *CloudHypervisor) Execve(args types.ExecArgs, ukernel types.Unikernel) blockArgs := ukernel.MonitorBlockCli() for _, blockArg := range blockArgs { if blockArg.ExactArgs != "" { - cmdString += blockArg.ExactArgs + exArgs = append(exArgs, strings.Split(strings.TrimSpace(blockArg.ExactArgs), " ")...) } else if blockArg.Path != "" { - cmdString += fmt.Sprintf(" --disk path=%s", blockArg.Path) + diskArg := fmt.Sprintf("path=%s", blockArg.Path) if blockArg.ID != "" { - cmdString += fmt.Sprintf(",id=%s", blockArg.ID) + diskArg += fmt.Sprintf(",id=%s", blockArg.ID) } + exArgs = append(exArgs, "--disk", diskArg) } } // Initrd configuration if args.InitrdPath != "" { - cmdString += " --initramfs " + args.InitrdPath + exArgs = append(exArgs, "--initramfs", args.InitrdPath) } // Check for extra initrd from unikernel monitor args extraMonArgs := ukernel.MonitorCli() if extraMonArgs.ExtraInitrd != "" && args.InitrdPath == "" { - cmdString += " --initramfs " + extraMonArgs.ExtraInitrd + exArgs = append(exArgs, "--initramfs", extraMonArgs.ExtraInitrd) } switch args.Sharedfs.Type { case "virtiofs": - cmdString += fmt.Sprintf(" --fs tag=fs0,socket=/tmp/virtiofsd.sock") + exArgs = append(exArgs, "--fs", "tag=fs0,socket=/tmp/vhostqemu") default: // No shared filesystem } if args.VAccelType == "vsock" { - cmdString += fmt.Sprintf(" --vsock cid=%d,socket=%s/vaccel.sock", - args.VSockDevID, args.VSockDevPath) + exArgs = append(exArgs, "--vsock", fmt.Sprintf("cid=%d,socket=%s/vaccel.sock", + args.VSockDevID, args.VSockDevPath)) } - cmdString += extraMonArgs.OtherArgs - - exArgs := strings.Split(cmdString, " ") + if extraMonArgs.OtherArgs != "" { + exArgs = append(exArgs, strings.Split(strings.TrimSpace(extraMonArgs.OtherArgs), " ")...) + } // Add the command line arguments for the kernel exArgs = append(exArgs, "--cmdline", args.Command) diff --git a/pkg/unikontainers/hypervisors/vmm.go b/pkg/unikontainers/hypervisors/vmm.go index 25c906fe..e520edb2 100644 --- a/pkg/unikontainers/hypervisors/vmm.go +++ b/pkg/unikontainers/hypervisors/vmm.go @@ -53,8 +53,10 @@ var vmmFactories = map[VmmType]VMMFactory{ createFunc: func(binary, binaryPath string) types.VMM { return &Firecracker{binary: binary, binaryPath: binaryPath} }, }, CloudHypervisorVmm: { - binary: CloudHypervisorBinary, - createFunc: func(binary, binaryPath string) types.VMM { return &CloudHypervisor{binary: binary, binaryPath: binaryPath} }, + binary: CloudHypervisorBinary, + createFunc: func(binary, binaryPath string) types.VMM { + return &CloudHypervisor{binary: binary, binaryPath: binaryPath} + }, }, } diff --git a/pkg/unikontainers/ipc.go b/pkg/unikontainers/ipc.go index b913dfd6..495b1e30 100644 --- a/pkg/unikontainers/ipc.go +++ b/pkg/unikontainers/ipc.go @@ -165,7 +165,7 @@ func AwaitMessage(listener *net.UnixListener, expectedMessage IPCMessage) error } msg := string(buf[0:n]) if msg != string(expectedMessage) { - return fmt.Errorf("received unexpected message: %s", msg) + return fmt.Errorf("received unexpected message: %s (expected %s)", msg, expectedMessage) } return nil } diff --git a/pkg/unikontainers/mount.go b/pkg/unikontainers/mount.go index 5ed27ec5..6173ea47 100644 --- a/pkg/unikontainers/mount.go +++ b/pkg/unikontainers/mount.go @@ -64,7 +64,7 @@ func createTmpfs(monRootfs string, path string, flags uint64, mode string, size if mode == "1777" { // sonarcloud:go:S2612 -- This is a tmpfs mount point, sticky bit 1777 is required (like /tmp), controlled path, safe by design - err := os.Chmod(path, 01777) // NOSONAR + err := os.Chmod(dstPath, 01777) // NOSONAR if err != nil { return fmt.Errorf("failed to chmod %s: %w", path, err) } @@ -116,6 +116,23 @@ func setupDev(monRootfs string, devPath string) error { // Create the new device node err = unix.Mknod(dstPath, devStat.Mode, int(newDev)) //nolint: gosec if err != nil { + // If mknod fails because of permissions (e.g. in a container), try to bind mount it + if errors.Is(err, unix.EPERM) || errors.Is(err, unix.EACCES) { + uniklog.Warnf("failed to make device node %s: %v. Fallback to bind mount.", dstPath, err) + // Create an empty file to be used as mount point + f, err1 := os.Create(dstPath) + if err1 != nil && !os.IsExist(err1) { + return fmt.Errorf("failed to create mount point for device %s: %w", dstPath, err1) + } + if f != nil { + f.Close() + } + err = unix.Mount(devPath, dstPath, "", unix.MS_BIND, "") + if err != nil { + return fmt.Errorf("failed to bind mount device %s: %w", devPath, err) + } + return nil + } return fmt.Errorf("failed to make device node %s: %w", dstPath, err) } @@ -188,15 +205,17 @@ func fileFromHost(monRootfs string, hostPath string, target string, mFlags int, } } - // Set up the permissions and ownership of the original file. - err = unix.Chmod(dstPath, fileInfo.Mode) - if err != nil { - return fmt.Errorf("failed to chmod %s: %w", dstPath, err) - } + if withCopy { + // Set up the permissions and ownership of the original file. + err = unix.Chmod(dstPath, fileInfo.Mode) + if err != nil { + return fmt.Errorf("failed to chmod %s: %w", dstPath, err) + } - err = os.Chown(dstPath, int(fileInfo.Uid), int(fileInfo.Gid)) - if err != nil { - return fmt.Errorf("failed to chown %s: %w", dstPath, err) + err = os.Chown(dstPath, int(fileInfo.Uid), int(fileInfo.Gid)) + if err != nil { + return fmt.Errorf("failed to chown %s: %w", dstPath, err) + } } // The initial MS_BIND won't change the mount options, we need to do a diff --git a/pkg/unikontainers/urunc_config.go b/pkg/unikontainers/urunc_config.go index 99616d48..56848da4 100644 --- a/pkg/unikontainers/urunc_config.go +++ b/pkg/unikontainers/urunc_config.go @@ -80,10 +80,11 @@ func defaultTimestampsConfig() UruncTimestamps { func defaultMonitorsConfig() map[string]types.MonitorConfig { return map[string]types.MonitorConfig{ - "qemu": {DefaultMemoryMB: 256, DefaultVCPUs: 1}, - "hvt": {DefaultMemoryMB: 256, DefaultVCPUs: 1}, - "spt": {DefaultMemoryMB: 256, DefaultVCPUs: 1}, - "firecracker": {DefaultMemoryMB: 256, DefaultVCPUs: 1}, + "qemu": {DefaultMemoryMB: 256, DefaultVCPUs: 1}, + "hvt": {DefaultMemoryMB: 256, DefaultVCPUs: 1}, + "spt": {DefaultMemoryMB: 256, DefaultVCPUs: 1}, + "firecracker": {DefaultMemoryMB: 256, DefaultVCPUs: 1}, + "cloud-hypervisor": {DefaultMemoryMB: 256, DefaultVCPUs: 1}, } } diff --git a/pkg/unikontainers/urunc_config_test.go b/pkg/unikontainers/urunc_config_test.go index b353bd22..a148da47 100644 --- a/pkg/unikontainers/urunc_config_test.go +++ b/pkg/unikontainers/urunc_config_test.go @@ -438,11 +438,12 @@ func TestDefaultConfigs(t *testing.T) { t.Parallel() config := defaultMonitorsConfig() - assert.Len(t, config, 4) + assert.Len(t, config, 5) assert.Contains(t, config, "qemu") assert.Contains(t, config, "hvt") assert.Contains(t, config, "spt") assert.Contains(t, config, "firecracker") + assert.Contains(t, config, "cloud-hypervisor") // Check default values for each monitor for _, hvConfig := range config { @@ -472,7 +473,7 @@ func TestDefaultConfigs(t *testing.T) { assert.False(t, config.Log.Syslog) assert.False(t, config.Timestamps.Enabled) assert.Equal(t, testTimestampsPath, config.Timestamps.Destination) - assert.Len(t, config.Monitors, 4) + assert.Len(t, config.Monitors, 5) assert.Len(t, config.ExtraBins, 1) }) diff --git a/tests/e2e/common.go b/tests/e2e/common.go index 6308dd02..d785f4d5 100644 --- a/tests/e2e/common.go +++ b/tests/e2e/common.go @@ -78,6 +78,7 @@ func commonCmdExec(command string) (output string, err error) { var stderrBuf bytes.Buffer params := strings.Fields(command) + fmt.Printf("Executing command: %s\n", command) cmd := exec.Command(params[0], params[1:]...) //nolint:gosec cmd.Stderr = &stderrBuf outBytes, err := cmd.Output() @@ -224,7 +225,13 @@ func findValOfKey(searchArea string, key string) (string, error) { return "", err } match := r.FindString(searchArea) + if match == "" { + return "", fmt.Errorf("key %s not found in search area", key) + } keyValMatch := strings.Split(match, ":") + if len(keyValMatch) < 2 { + return "", fmt.Errorf("invalid format for key %s: %s", key, match) + } val := strings.ReplaceAll(keyValMatch[1], "\"", "") return strings.TrimSpace(val), nil } diff --git a/tests/e2e/nerdctl.go b/tests/e2e/nerdctl.go index 2dd79f1d..1a0fe5a8 100644 --- a/tests/e2e/nerdctl.go +++ b/tests/e2e/nerdctl.go @@ -125,7 +125,10 @@ func (i *nerdctlInfo) inspectCAndGet(key string) (string, error) { return commonInspectCAndGet(nerdctlName, i.containerID, key) } -func (i *nerdctlInfo) inspectPAndGet(string) (string, error) { +func (i *nerdctlInfo) inspectPAndGet(key string) (string, error) { + if key == "pid" || key == "Pid" { + return i.inspectCAndGet("Pid") + } // Not supported by nerdctl return "", errToolDoesNotSupport } diff --git a/tests/e2e/test_cases.go b/tests/e2e/test_cases.go index 958d5fb5..f45f5987 100644 --- a/tests/e2e/test_cases.go +++ b/tests/e2e/test_cases.go @@ -413,41 +413,41 @@ func nerdctlTestCases(kvmGroup ...int64) []containerTestArgs { TestFunc: blockMountTest, }, { - Image: "harbor.nbfc.io/nubificus/urunc/nginx-cloud-hypervisor-unikraft-initrd:latest", - Name: "CloudHypervisor-unikraft-ping-nginx", + Image: "harbor.nbfc.io/nubificus/urunc/busybox-cloud-hypervisor-linux-raw:latest", + Name: "CloudHypervisor-linux-ping", Devmapper: false, Seccomp: true, UID: 0, GID: 0, Groups: []int64{}, - Memory: "", + Memory: "512M", Cli: "", Volumes: []containerVolume{}, StaticNet: false, SideContainers: []string{}, - Skippable: true, // Skip until Cloud Hypervisor images are available + Skippable: false, TestFunc: pingTest, }, { - Image: "harbor.nbfc.io/nubificus/urunc/nginx-cloud-hypervisor-unikraft-initrd:latest", - Name: "CloudHypervisor-unikraft-with-seccomp", + Image: "harbor.nbfc.io/nubificus/urunc/busybox-cloud-hypervisor-linux-raw:latest", + Name: "CloudHypervisor-linux-seccomp", Devmapper: false, Seccomp: true, UID: 0, GID: 0, Groups: []int64{}, - Memory: "", - Cli: "", + Memory: "512M", + Cli: "/bin/busybox tail -f /dev/null", Volumes: []containerVolume{}, StaticNet: false, SideContainers: []string{}, - Skippable: true, // Skip until Cloud Hypervisor images are available + Skippable: false, TestFunc: seccompTest, }, { Image: "harbor.nbfc.io/nubificus/urunc/nginx-cloud-hypervisor-linux-raw:latest", Name: "CloudHypervisor-linux-nginx", - Devmapper: true, + Devmapper: false, Seccomp: true, UID: 0, GID: 0, @@ -457,7 +457,7 @@ func nerdctlTestCases(kvmGroup ...int64) []containerTestArgs { Volumes: []containerVolume{}, StaticNet: false, SideContainers: []string{}, - Skippable: true, // Skip until Cloud Hypervisor images are available + Skippable: false, TestFunc: pingTest, }, } diff --git a/tests/e2e/test_functions.go b/tests/e2e/test_functions.go index 99a3ccf6..f9a453dc 100644 --- a/tests/e2e/test_functions.go +++ b/tests/e2e/test_functions.go @@ -169,7 +169,28 @@ func seccompTest(tool testTool) error { if len(wordsInLine) != 2 { return fmt.Errorf("Invalid format of line. Expecting 2 values, got %d", len(wordsInLine)) } - if strings.TrimSpace(wordsInLine[1]) == "2" { + seccompStatus := strings.TrimSpace(wordsInLine[1]) + + // If main thread doesn't have seccomp, check other threads + if seccompStatus == "0" && args.Seccomp { + taskPath := "/proc/" + unikernelPID + "/task" + tasks, err := os.ReadDir(taskPath) + if err == nil { + for _, task := range tasks { + threadStatusPath := filepath.Join(taskPath, task.Name(), "status") + line, err := findLineInFile(threadStatusPath, "Seccomp") + if err == nil { + words := strings.Split(line, ":") + if len(words) == 2 && strings.TrimSpace(words[1]) == "2" { + seccompStatus = "2" + break + } + } + } + } + } + + if seccompStatus == "2" { if !args.Seccomp { return fmt.Errorf("Seccomp should not be enabled") } diff --git a/tests/e2e/utils.go b/tests/e2e/utils.go index 89d2aed6..316c3b27 100644 --- a/tests/e2e/utils.go +++ b/tests/e2e/utils.go @@ -32,8 +32,9 @@ func pingUnikernel(ipAddress string) error { if err != nil { return fmt.Errorf("failed to create Pinger: %v", err) } - pinger.Count = 3 - pinger.Timeout = 5 * time.Second + pinger.SetPrivileged(true) // Enable privileged mode to use ICMP + pinger.Count = 10 + pinger.Timeout = 15 * time.Second err = pinger.Run() if err != nil { return fmt.Errorf("failed to ping %s: %v", ipAddress, err)