168 lines
6.3 KiB
Go
168 lines
6.3 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
//go:build linux
|
|
|
|
package cgutil
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/nomad/client/testutil"
|
|
"github.com/hashicorp/nomad/helper/testlog"
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
"github.com/hashicorp/nomad/lib/cpuset"
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func tmpCpusetManagerV1(t *testing.T) (*cpusetManagerV1, func()) {
|
|
mount, err := FindCgroupMountpointDir()
|
|
if err != nil || mount == "" {
|
|
t.Skipf("Failed to find cgroup mount: %v %v", mount, err)
|
|
}
|
|
|
|
parent := "/gotest-" + uuid.Short()
|
|
require.NoError(t, cpusetEnsureParentV1(parent))
|
|
|
|
parentPath, err := GetCgroupPathHelperV1("cpuset", parent)
|
|
require.NoError(t, err)
|
|
|
|
manager := NewCpusetManagerV1(parent, nil, testlog.HCLogger(t)).(*cpusetManagerV1)
|
|
return manager, func() { require.NoError(t, cgroups.RemovePaths(map[string]string{"cpuset": parentPath})) }
|
|
}
|
|
|
|
func TestCpusetManager_V1_Init(t *testing.T) {
|
|
testutil.CgroupsCompatibleV1(t)
|
|
|
|
manager, cleanup := tmpCpusetManagerV1(t)
|
|
defer cleanup()
|
|
manager.Init()
|
|
|
|
require.DirExists(t, filepath.Join(manager.cgroupParentPath, SharedCpusetCgroupName))
|
|
require.FileExists(t, filepath.Join(manager.cgroupParentPath, SharedCpusetCgroupName, "cpuset.cpus"))
|
|
sharedCpusRaw, err := os.ReadFile(filepath.Join(manager.cgroupParentPath, SharedCpusetCgroupName, "cpuset.cpus"))
|
|
require.NoError(t, err)
|
|
sharedCpus, err := cpuset.Parse(string(sharedCpusRaw))
|
|
require.NoError(t, err)
|
|
require.Exactly(t, manager.parentCpuset.ToSlice(), sharedCpus.ToSlice())
|
|
require.DirExists(t, filepath.Join(manager.cgroupParentPath, ReservedCpusetCgroupName))
|
|
}
|
|
|
|
func TestCpusetManager_V1_AddAlloc_single(t *testing.T) {
|
|
testutil.CgroupsCompatibleV1(t)
|
|
|
|
manager, cleanup := tmpCpusetManagerV1(t)
|
|
defer cleanup()
|
|
manager.Init()
|
|
|
|
alloc := mock.Alloc()
|
|
// reserve just one core (the 0th core, which probably exists)
|
|
alloc.AllocatedResources.Tasks["web"].Cpu.ReservedCores = cpuset.New(0).ToSlice()
|
|
manager.AddAlloc(alloc)
|
|
|
|
// force reconcile
|
|
manager.reconcileCpusets()
|
|
|
|
// check that the 0th core is no longer available in the shared group
|
|
// actual contents of shared group depends on machine core count
|
|
require.DirExists(t, filepath.Join(manager.cgroupParentPath, SharedCpusetCgroupName))
|
|
require.FileExists(t, filepath.Join(manager.cgroupParentPath, SharedCpusetCgroupName, "cpuset.cpus"))
|
|
sharedCpusRaw, err := os.ReadFile(filepath.Join(manager.cgroupParentPath, SharedCpusetCgroupName, "cpuset.cpus"))
|
|
require.NoError(t, err)
|
|
sharedCpus, err := cpuset.Parse(string(sharedCpusRaw))
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, sharedCpus.ToSlice())
|
|
require.NotContains(t, sharedCpus.ToSlice(), uint16(0))
|
|
|
|
// check that the 0th core is allocated to reserved cgroup
|
|
require.DirExists(t, filepath.Join(manager.cgroupParentPath, ReservedCpusetCgroupName))
|
|
reservedCpusRaw, err := os.ReadFile(filepath.Join(manager.cgroupParentPath, ReservedCpusetCgroupName, "cpuset.cpus"))
|
|
require.NoError(t, err)
|
|
reservedCpus, err := cpuset.Parse(string(reservedCpusRaw))
|
|
require.NoError(t, err)
|
|
require.Exactly(t, alloc.AllocatedResources.Tasks["web"].Cpu.ReservedCores, reservedCpus.ToSlice())
|
|
|
|
// check that task cgroup exists and cpuset matches expected reserved cores
|
|
allocInfo, ok := manager.cgroupInfo[alloc.ID]
|
|
require.True(t, ok)
|
|
require.Len(t, allocInfo, 1)
|
|
taskInfo, ok := allocInfo["web"]
|
|
require.True(t, ok)
|
|
|
|
require.DirExists(t, taskInfo.CgroupPath)
|
|
taskCpusRaw, err := os.ReadFile(filepath.Join(taskInfo.CgroupPath, "cpuset.cpus"))
|
|
require.NoError(t, err)
|
|
taskCpus, err := cpuset.Parse(string(taskCpusRaw))
|
|
require.NoError(t, err)
|
|
require.Exactly(t, alloc.AllocatedResources.Tasks["web"].Cpu.ReservedCores, taskCpus.ToSlice())
|
|
}
|
|
|
|
func TestCpusetManager_V1_RemoveAlloc(t *testing.T) {
|
|
testutil.CgroupsCompatibleV1(t)
|
|
|
|
// This case tests adding 2 allocations, reconciling then removing 1 alloc.
|
|
// It requires the system to have at least 3 cpu cores (one for each alloc),
|
|
// BUT plus another one because writing an empty cpuset causes the cgroup to
|
|
// inherit the parent.
|
|
testutil.MinimumCores(t, 3)
|
|
|
|
manager, cleanup := tmpCpusetManagerV1(t)
|
|
defer cleanup()
|
|
manager.Init()
|
|
|
|
alloc1 := mock.Alloc()
|
|
alloc1Cpuset := cpuset.New(manager.parentCpuset.ToSlice()[0])
|
|
alloc1.AllocatedResources.Tasks["web"].Cpu.ReservedCores = alloc1Cpuset.ToSlice()
|
|
manager.AddAlloc(alloc1)
|
|
|
|
alloc2 := mock.Alloc()
|
|
alloc2Cpuset := cpuset.New(manager.parentCpuset.ToSlice()[1])
|
|
alloc2.AllocatedResources.Tasks["web"].Cpu.ReservedCores = alloc2Cpuset.ToSlice()
|
|
manager.AddAlloc(alloc2)
|
|
|
|
//force reconcile
|
|
manager.reconcileCpusets()
|
|
|
|
// shared cpuset should not include any expected cores
|
|
sharedCpusRaw, err := os.ReadFile(filepath.Join(manager.cgroupParentPath, SharedCpusetCgroupName, "cpuset.cpus"))
|
|
require.NoError(t, err)
|
|
sharedCpus, err := cpuset.Parse(string(sharedCpusRaw))
|
|
require.NoError(t, err)
|
|
require.False(t, sharedCpus.ContainsAny(alloc1Cpuset.Union(alloc2Cpuset)))
|
|
|
|
// reserved cpuset should equal the expected cpus
|
|
reservedCpusRaw, err := os.ReadFile(filepath.Join(manager.cgroupParentPath, ReservedCpusetCgroupName, "cpuset.cpus"))
|
|
require.NoError(t, err)
|
|
reservedCpus, err := cpuset.Parse(string(reservedCpusRaw))
|
|
require.NoError(t, err)
|
|
require.True(t, reservedCpus.Equal(alloc1Cpuset.Union(alloc2Cpuset)))
|
|
|
|
// remove first allocation
|
|
alloc1TaskPath := manager.cgroupInfo[alloc1.ID]["web"].CgroupPath
|
|
manager.RemoveAlloc(alloc1.ID)
|
|
manager.reconcileCpusets()
|
|
|
|
// alloc1's task reserved cgroup should be removed
|
|
require.NoDirExists(t, alloc1TaskPath)
|
|
|
|
// shared cpuset should now include alloc1's cores
|
|
sharedCpusRaw, err = os.ReadFile(filepath.Join(manager.cgroupParentPath, SharedCpusetCgroupName, "cpuset.cpus"))
|
|
require.NoError(t, err)
|
|
sharedCpus, err = cpuset.Parse(string(sharedCpusRaw))
|
|
require.NoError(t, err)
|
|
require.False(t, sharedCpus.ContainsAny(alloc2Cpuset))
|
|
require.True(t, sharedCpus.IsSupersetOf(alloc1Cpuset))
|
|
|
|
// reserved cpuset should only include alloc2's cores
|
|
reservedCpusRaw, err = os.ReadFile(filepath.Join(manager.cgroupParentPath, ReservedCpusetCgroupName, "cpuset.cpus"))
|
|
require.NoError(t, err)
|
|
reservedCpus, err = cpuset.Parse(string(reservedCpusRaw))
|
|
require.NoError(t, err)
|
|
require.True(t, reservedCpus.Equal(alloc2Cpuset))
|
|
|
|
}
|