Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions pkg/unikontainers/hypervisors/cloud_hypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,19 @@ func (ch *CloudHypervisor) Path() string {
return ch.binaryPath
}

// BuildExecCmd builds and validates the Cloud Hypervisor command arguments without executing.
func (ch *CloudHypervisor) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel) ([]string, error) {
chMem := BytesToStringMB(args.MemSizeB)

// Start building the command
exArgs := []string{ch.binaryPath}

// Memory configuration
memSize := args.MemSizeB
if memSize == 0 {
memSize = DefaultMemory * 1024 * 1024
}
if args.Sharedfs.Type == "virtiofs" {
exArgs = append(exArgs, "--memory", fmt.Sprintf("size=%sM,shared=on", chMem))
exArgs = append(exArgs, "--memory", fmt.Sprintf("size=%d,shared=on", memSize))
} else {
exArgs = append(exArgs, "--memory", fmt.Sprintf("size=%sM", chMem))
exArgs = append(exArgs, "--memory", fmt.Sprintf("size=%d", memSize))
}

// CPU configuration
Expand Down
6 changes: 3 additions & 3 deletions pkg/unikontainers/hypervisors/firecracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ func (fc *Firecracker) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel
fcMem := DefaultMemory
if args.MemSizeB != 0 {
fcMem = bytesToMiB(args.MemSizeB)
// Check if memory is too small
if fcMem == 0 {
fcMem = DefaultMemory
if args.MemSizeB < 1024*1024 {
fcMem = 1
vmmLog.Warnf("Requested memory (%d bytes) is below Firecracker's minimum unit (1 MiB); clamping to 1 MiB", args.MemSizeB)
}
}
// NOTE: Firecracker supports only one initrd.
Expand Down
6 changes: 5 additions & 1 deletion pkg/unikontainers/hypervisors/hvt.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ func (h *HVT) Ok() error {
}

func (h *HVT) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel) ([]string, error) {
hvtMem := BytesToStringMB(args.MemSizeB)
hvtMem := BytesToMiBString(args.MemSizeB)
if args.MemSizeB > 0 && args.MemSizeB < 1024*1024 {
hvtMem = "1"
vmmLog.Warnf("Requested memory (%d bytes) is below Solo5's minimum unit (1 MiB); clamping to 1 MiB", args.MemSizeB)
}
cmdString := h.binaryPath + " --mem=" + hvtMem
if args.Net.TapDev != "" {
cmdString += " "
Expand Down
99 changes: 99 additions & 0 deletions pkg/unikontainers/hypervisors/hvt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) 2023-2026, 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 (
"testing"

"github.com/stretchr/testify/assert"
"github.com/urunc-dev/urunc/pkg/unikontainers/types"
)

func TestHVTBuildExecCmd(t *testing.T) {
t.Parallel()

h := &HVT{
binaryPath: "/usr/local/bin/solo5-hvt",
binary: "solo5-hvt",
}

tests := []struct {
name string
args types.ExecArgs
unikernel types.Unikernel
mustContain []string
}{
{
name: "defaults render default memory",
args: types.ExecArgs{
UnikernelPath: "/path/to/unikernel",
Command: "hello",
},
unikernel: &fakeUnikernel{},
mustContain: []string{
"/usr/local/bin/solo5-hvt",
"--mem=256",
"/path/to/unikernel",
"hello",
},
},
{
name: "custom memory above 1 MiB",
args: types.ExecArgs{
UnikernelPath: "/path/to/unikernel",
Command: "hello",
MemSizeB: 16 * 1024 * 1024, // 16 MiB
},
unikernel: &fakeUnikernel{},
mustContain: []string{
"--mem=16",
},
},
{
name: "custom memory exactly 1 MiB",
args: types.ExecArgs{
UnikernelPath: "/path/to/unikernel",
Command: "hello",
MemSizeB: 1024 * 1024, // 1 MiB
},
unikernel: &fakeUnikernel{},
mustContain: []string{
"--mem=1",
},
},
{
name: "custom memory sub-1-MiB clamps to 1 MiB",
args: types.ExecArgs{
UnikernelPath: "/path/to/unikernel",
Command: "hello",
MemSizeB: 512 * 1024, // 512 KiB
},
unikernel: &fakeUnikernel{},
mustContain: []string{
"--mem=1",
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cmdArgs, err := h.BuildExecCmd(tc.args, tc.unikernel)
assert.NoError(t, err)
for _, expected := range tc.mustContain {
assert.Contains(t, cmdArgs, expected)
}
})
}
}
4 changes: 2 additions & 2 deletions pkg/unikontainers/hypervisors/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func (q *Qemu) Path() string {
}

func (q *Qemu) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel) ([]string, error) {
qemuMem := BytesToStringMB(args.MemSizeB)
cmdString := q.binaryPath + " -m " + qemuMem + "M"
qemuMem := BytesToBString(args.MemSizeB)
cmdString := q.binaryPath + " -m " + qemuMem + "B"
cmdString += " -L /usr/share/qemu" // Set the path for qemu bios/data
cmdString += " -cpu host" // Choose CPU
cmdString += " -enable-kvm" // Enable KVM to use CPU virt extensions
Expand Down
6 changes: 3 additions & 3 deletions pkg/unikontainers/hypervisors/qemu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func TestQemuBuildExecCmd(t *testing.T) {
"-vga none",
"-serial stdio",
"-monitor null",
"-m 256M",
"-m 268435456B",
"-kernel " + testKernelPath,
"-nic none",
},
Expand All @@ -95,14 +95,14 @@ func TestQemuBuildExecCmd(t *testing.T) {
},
},
{
name: "custom MemSizeB renders -m in MB",
name: "custom MemSizeB renders -m in Bytes",
args: types.ExecArgs{
UnikernelPath: testKernelPath,
Command: testCommand,
MemSizeB: 512 * 1000 * 1000,
},
unikernel: &fakeUnikernel{},
mustContain: []string{"-m 512M"},
mustContain: []string{"-m 512000000B"},
},
{
name: "VCPUs set emits -smp",
Expand Down
6 changes: 5 additions & 1 deletion pkg/unikontainers/hypervisors/spt.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ func (s *SPT) Ok() error {
}

func (s *SPT) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel) ([]string, error) {
sptMem := BytesToStringMB(args.MemSizeB)
sptMem := BytesToMiBString(args.MemSizeB)
if args.MemSizeB > 0 && args.MemSizeB < 1024*1024 {
sptMem = "1"
vmmLog.Warnf("Requested memory (%d bytes) is below Solo5's minimum unit (1 MiB); clamping to 1 MiB", args.MemSizeB)
}
cmdString := s.binaryPath + " --mem=" + sptMem
if args.Net.TapDev != "" {
cmdString += " "
Expand Down
99 changes: 99 additions & 0 deletions pkg/unikontainers/hypervisors/spt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) 2023-2026, 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 (
"testing"

"github.com/stretchr/testify/assert"
"github.com/urunc-dev/urunc/pkg/unikontainers/types"
)

func TestSPTBuildExecCmd(t *testing.T) {
t.Parallel()

s := &SPT{
binaryPath: "/usr/local/bin/solo5-spt",
binary: "solo5-spt",
}

tests := []struct {
name string
args types.ExecArgs
unikernel types.Unikernel
mustContain []string
}{
{
name: "defaults render default memory",
args: types.ExecArgs{
UnikernelPath: "/path/to/unikernel",
Command: "hello",
},
unikernel: &fakeUnikernel{},
mustContain: []string{
"/usr/local/bin/solo5-spt",
"--mem=256",
"/path/to/unikernel",
"hello",
},
},
{
name: "custom memory above 1 MiB",
args: types.ExecArgs{
UnikernelPath: "/path/to/unikernel",
Command: "hello",
MemSizeB: 16 * 1024 * 1024, // 16 MiB
},
unikernel: &fakeUnikernel{},
mustContain: []string{
"--mem=16",
},
},
{
name: "custom memory exactly 1 MiB",
args: types.ExecArgs{
UnikernelPath: "/path/to/unikernel",
Command: "hello",
MemSizeB: 1024 * 1024, // 1 MiB
},
unikernel: &fakeUnikernel{},
mustContain: []string{
"--mem=1",
},
},
{
name: "custom memory sub-1-MiB clamps to 1 MiB",
args: types.ExecArgs{
UnikernelPath: "/path/to/unikernel",
Command: "hello",
MemSizeB: 512 * 1024, // 512 KiB
},
unikernel: &fakeUnikernel{},
mustContain: []string{
"--mem=1",
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cmdArgs, err := s.BuildExecCmd(tc.args, tc.unikernel)
assert.NoError(t, err)
for _, expected := range tc.mustContain {
assert.Contains(t, cmdArgs, expected)
}
})
}
}
24 changes: 10 additions & 14 deletions pkg/unikontainers/hypervisors/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,19 @@ func bytesToMiB(bytes uint64) uint64 {
return bytes / bytesInMiB
}

func bytesToMB(bytes uint64) uint64 {
const bytesInMB = 1000 * 1000
return bytes / bytesInMB
func BytesToBString(argMem uint64) string {
if argMem == 0 {
return strconv.FormatUint(DefaultMemory*1024*1024, 10)
}
return strconv.FormatUint(argMem, 10)
}

func BytesToStringMB(argMem uint64) string {
stringMem := strconv.FormatUint(DefaultMemory, 10)
if argMem != 0 {
userMem := bytesToMB(argMem)
// Check for too low memory
if userMem == 0 {
userMem = DefaultMemory
}
stringMem = strconv.FormatUint(userMem, 10)
func BytesToMiBString(argMem uint64) string {
if argMem == 0 {
return strconv.FormatUint(DefaultMemory, 10)
}

return stringMem
userMem := bytesToMiB(argMem)
return strconv.FormatUint(userMem, 10)
}

func killProcess(pid int) error {
Expand Down
41 changes: 31 additions & 10 deletions pkg/unikontainers/hypervisors/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,28 +46,49 @@ func TestBytesToMiB(t *testing.T) {
}
}

func TestBytesToMB(t *testing.T) {
func TestBytesToBString(t *testing.T) {
t.Parallel()

const mb uint64 = 1000 * 1000
cases := []struct {
name string
input uint64
expected string
}{
{"zero (Case A)", 0, "268435456"}, // 256 MB * 1024 * 1024
{"exactly 512 bytes", 512, "512"},
{"exactly 1024 bytes", 1024, "1024"},
{"large value", 1024 * 1024 * 1024, "1073741824"},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.expected, BytesToBString(tc.input))
})
}
}

func TestBytesToMiBString(t *testing.T) {
t.Parallel()

const mib uint64 = 1024 * 1024

cases := []struct {
name string
input uint64
expected uint64
expected string
}{
{"zero", 0, 0},
{"less than one MB truncates to zero", mb - 1, 0},
{"exactly one MB", mb, 1},
{"exactly two MB", 2 * mb, 2},
{"non-multiple truncates down", mb + (mb / 2), 1},
{"large value", 1024 * mb, 1024},
{"zero (Case A)", 0, "256"},
{"less than one MiB (Case B/C)", mib - 1, "0"},
{"exactly one MiB", mib, "1"},
{"exactly two MiB", 2 * mib, "2"},
{"large value", 1024 * mib, "1024"},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.expected, bytesToMB(tc.input))
assert.Equal(t, tc.expected, BytesToMiBString(tc.input))
})
}
}
Loading