Files
nomad/drivers/shared/executor/executor_universal_linux_test.go
Seth Hoenig b58abf48c1 drivers: move executor process out of v1 task cgroup after process starts (#24340)
* drivers: move executor process out of v1 task cgroup after process starts

This PR changes the behavior of the raw exec task driver on old cgroups v1
systems such that the executor process is no longer a member of the cgroups
created for the task. Now, the executor process is placed into those
cgroups and starts the task child process (just as before), but now then
exits those cgroups and exists in the nomad parent cgroup. This change
makes the behavior sort of similar to cgroups v2 systems, where we never
have the executor enter the task cgroup to begin with (because we can
directly clone(3) the task process into it).

Fixes #23951

* executor: handle non-linux case

* cgroups: add test case for no executor process in task cgroup (v1)

* add changelog

* drivers: also move executor out of cpuset cgroup
2024-11-07 07:31:38 -06:00

165 lines
4.2 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build linux
package executor
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/client/lib/cgroupslib"
"github.com/hashicorp/nomad/client/testutil"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/plugins/drivers"
"github.com/shoenig/test/must"
)
func Test_computeMemory(t *testing.T) {
cases := []struct {
memory int64
memoryMax int64
expSoft int64
expHard int64
}{
{
// typical case; only 'memory' is set and that is used as the hard
// memory limit
memory: 100,
memoryMax: 0,
expSoft: 0,
expHard: mbToBytes(100),
},
{
// oversub case; both 'memory' and 'memory_max' are set and used as
// the soft and hard memory limits
memory: 100,
memoryMax: 200,
expSoft: mbToBytes(100),
expHard: mbToBytes(200),
},
{
// special oversub case; 'memory' is set and 'memory_max' is set to
// -1; which indicates there should be no hard limit (i.e. -1 / max)
memory: 100,
memoryMax: memoryNoLimit,
expSoft: mbToBytes(100),
expHard: memoryNoLimit,
},
}
for _, tc := range cases {
name := fmt.Sprintf("(%d,%d)", tc.memory, tc.memoryMax)
t.Run(name, func(t *testing.T) {
command := &ExecCommand{
Resources: &drivers.Resources{
NomadResources: &structs.AllocatedTaskResources{
Memory: structs.AllocatedMemoryResources{
MemoryMB: tc.memory,
MemoryMaxMB: tc.memoryMax,
},
},
},
}
hard, soft := (*UniversalExecutor)(nil).computeMemory(command)
must.Eq(t, tc.expSoft, soft)
must.Eq(t, tc.expHard, hard)
})
}
}
func TestExecutor_InvalidCgroup(t *testing.T) {
ci.Parallel(t)
testutil.CgroupsCompatible(t)
factory := universalFactory
testExecCmd := testExecutorCommand(t)
execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
execCmd.Cmd = "sleep"
execCmd.Args = []string{"infinity"}
switch cgroupslib.GetMode() {
case cgroupslib.CG1:
execCmd.OverrideCgroupV1 = map[string]string{
"pid": "custom/path",
}
case cgroupslib.CG2:
execCmd.OverrideCgroupV2 = "custom.slice/test.scope"
}
factory.configureExecCmd(t, execCmd)
defer allocDir.Destroy()
executor := factory.new(testlog.HCLogger(t), compute)
defer executor.Shutdown("", 0)
_, err := executor.Launch(execCmd)
must.ErrorContains(t, err, "unable to configure cgroups: no such file or directory")
}
func TestUniversalExecutor_setOomAdj(t *testing.T) {
ci.Parallel(t)
factory := universalFactory
testExecCmd := testExecutorCommand(t)
execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
execCmd.Cmd = "sleep"
execCmd.Args = []string{"infinity"}
execCmd.OOMScoreAdj = 1000
factory.configureExecCmd(t, execCmd)
defer allocDir.Destroy()
executor := factory.new(testlog.HCLogger(t), compute)
defer executor.Shutdown("", 0)
p, err := executor.Launch(execCmd)
must.NoError(t, err)
oomScore, err := os.ReadFile(fmt.Sprintf("/proc/%d/oom_score_adj", p.Pid))
must.NoError(t, err)
oomScoreInt, _ := strconv.Atoi(strings.TrimSuffix(string(oomScore), "\n"))
must.Eq(t, execCmd.OOMScoreAdj, int32(oomScoreInt))
}
func TestUniversalExecutor_cg1_no_executor_pid(t *testing.T) {
testutil.CgroupsCompatibleV1(t)
ci.Parallel(t)
factory := universalFactory
testExecCmd := testExecutorCommand(t)
execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
execCmd.Cmd = "sleep"
execCmd.Args = []string{"infinity"}
factory.configureExecCmd(t, execCmd)
defer allocDir.Destroy()
executor := factory.new(testlog.HCLogger(t), compute)
defer executor.Shutdown("", 0)
p, err := executor.Launch(execCmd)
must.NoError(t, err)
alloc := filepath.Base(allocDir.AllocDirPath())
ifaces := []string{"cpu", "memory", "freezer"}
for _, iface := range ifaces {
cgroup := fmt.Sprintf("/sys/fs/cgroup/%s/nomad/%s.web/cgroup.procs", iface, alloc)
content, err := os.ReadFile(cgroup)
must.NoError(t, err)
// ensure only 1 pid (sleep) is present in this cgroup
pids := strings.Fields(string(content))
must.SliceLen(t, 1, pids)
must.Eq(t, pids[0], strconv.Itoa(p.Pid))
}
}