Skip to content
Merged
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
5 changes: 1 addition & 4 deletions hypervisor/cloudhypervisor/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package cloudhypervisor
import (
"fmt"
"path/filepath"
"runtime"
"strconv"
"strings"

Expand Down Expand Up @@ -43,10 +42,8 @@ func buildVMConfig(rec *hypervisor.VMRecord, consoleSockPath string, allowed []i
cpu := rec.Config.CPU
mem := rec.Config.Memory

maxVCPUs := runtime.NumCPU()

cfg := &chVMConfig{
CPUs: chCPUs{BootVCPUs: cpu, MaxVCPUs: maxVCPUs, KVMHyperV: rec.Config.Windows},
CPUs: chCPUs{BootVCPUs: cpu, MaxVCPUs: hypervisor.HostCPUCount(), KVMHyperV: rec.Config.Windows},
Memory: chMemory{Size: mem, HugePages: rec.Config.HugePages, Shared: rec.Config.SharedMemory},
RNG: chRNG{Src: "/dev/urandom"},
Watchdog: true,
Expand Down
19 changes: 18 additions & 1 deletion hypervisor/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/projecteru2/core/log"
"github.com/vishvananda/netns"

"github.com/cocoonstack/cocoon/cgroup"
"github.com/cocoonstack/cocoon/lock/flock"
"github.com/cocoonstack/cocoon/lock/vmlock"
"github.com/cocoonstack/cocoon/types"
Expand Down Expand Up @@ -48,6 +49,8 @@ const (

// socketReadyPollInterval is the WaitForSocket poll cadence — VMM socket usually appears within a few ms after process start.
socketReadyPollInterval = 1 * time.Millisecond

onlineCPUsPath = "/sys/devices/system/cpu/online"
)

// SnapshotFileKind classifies a snapshot file for CloneSnapshotFiles.
Expand Down Expand Up @@ -269,8 +272,22 @@ func MergeDirInto(src, dst string) error {
return nil
}

// HostCPUCount returns the host's online CPU count; runtime.NumCPU reports the process affinity mask, which undersizes guests when the control plane is core-pinned.
func HostCPUCount() int {
data, err := os.ReadFile(onlineCPUsPath)
if err != nil {
return runtime.NumCPU()
}
cpus, err := cgroup.ParseCPUList(strings.TrimSpace(string(data)))
if err != nil || len(cpus) == 0 {
return runtime.NumCPU()
}
return len(cpus)
}

// ValidateHostCPU rejects vCPU counts beyond the host's online cores.
func ValidateHostCPU(cpu int) error {
maxCPU := runtime.NumCPU()
maxCPU := HostCPUCount()
if cpu > maxCPU {
return fmt.Errorf("requested %d vCPUs exceeds host cores (%d)", cpu, maxCPU)
}
Expand Down
10 changes: 10 additions & 0 deletions hypervisor/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ package hypervisor
import (
"os"
"path/filepath"
"runtime"
"slices"
"strings"
"testing"

"github.com/cocoonstack/cocoon/types"
)

// Under a pinned test process (taskset) NumCPU shrinks to the affinity mask while HostCPUCount must keep reporting the machine.
func TestHostCPUCount(t *testing.T) {
got := HostCPUCount()
t.Logf("HostCPUCount=%d runtime.NumCPU=%d", got, runtime.NumCPU())
if got < runtime.NumCPU() {
t.Fatalf("HostCPUCount() = %d, below runtime.NumCPU() = %d", got, runtime.NumCPU())
}
}

func TestValidateSnapshotIntegrity(t *testing.T) {
dir := t.TempDir()
mustWrite := func(name string) {
Expand Down