Skip to content

feat: add agent exec pkg #15577

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Nov 25, 2024
Prev Previous commit
Next Next commit
more tests
  • Loading branch information
sreya committed Nov 20, 2024
commit 712b328fde102aa0cf098f8ee8e9f77dba5a7615
9 changes: 5 additions & 4 deletions agent/agentexec/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

// CLI runs the agent-exec command. It should only be called by the cli package.
func CLI(args []string, environ []string) error {
runtime.LockOSThread()

if runtime.GOOS != "linux" {
return xerrors.Errorf("agent-exec is only supported on Linux")
}
Expand All @@ -42,7 +44,6 @@
if err != nil {
return xerrors.Errorf("get default nice score: %w", err)
}
fmt.Println("nice score", nice, "pid", pid)
}

oomscore, ok := envValInt(environ, EnvProcOOMScore)
Expand All @@ -54,7 +55,7 @@
}
}

err = unix.Setpriority(unix.PRIO_PROCESS, pid, nice)
err = syscall.Setpriority(syscall.PRIO_PROCESS, 0, nice)

Check failure on line 58 in agent/agentexec/cli.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

undefined: syscall.Setpriority

Check failure on line 58 in agent/agentexec/cli.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

undefined: syscall.PRIO_PROCESS

Check failure on line 58 in agent/agentexec/cli.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

undefined: syscall.Setpriority

Check failure on line 58 in agent/agentexec/cli.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

undefined: syscall.PRIO_PROCESS
if err != nil {
return xerrors.Errorf("set nice score: %w", err)
}
Expand All @@ -78,12 +79,12 @@
}

func defaultNiceScore() (int, error) {
score, err := unix.Getpriority(unix.PRIO_PROCESS, os.Getpid())

Check failure on line 82 in agent/agentexec/cli.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

undefined: unix.Getpriority

Check failure on line 82 in agent/agentexec/cli.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

undefined: unix.PRIO_PROCESS

Check failure on line 82 in agent/agentexec/cli.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

undefined: unix.Getpriority

Check failure on line 82 in agent/agentexec/cli.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

undefined: unix.PRIO_PROCESS
if err != nil {
return 0, xerrors.Errorf("get nice score: %w", err)
}
// Priority is niceness + 20.
score -= 20
// See https://linux.die.net/man/2/setpriority#Notes
score = 20 - score

score += 5
if score > 19 {
Expand Down
22 changes: 13 additions & 9 deletions agent/agentexec/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ func TestCLI(t *testing.T) {

ctx := testutil.Context(t, testutil.WaitMedium)
cmd, path := cmd(ctx, t)
cmd.Env = append(cmd.Env, "CODER_PROC_NICE_SCORE=10")
cmd.Env = append(cmd.Env, "CODER_PROC_NICE_SCORE=12")
cmd.Env = append(cmd.Env, "CODER_PROC_OOM_SCORE=123")
err := cmd.Start()
require.NoError(t, err)
go cmd.Wait()

waitForSentinel(ctx, t, cmd, path)
requireOOMScore(t, cmd.Process.Pid, 123)
requireNiceScore(t, cmd.Process.Pid, 10)
requireNiceScore(t, cmd.Process.Pid, 12)
})

t.Run("Defaults", func(t *testing.T) {
Expand All @@ -50,7 +50,6 @@ func TestCLI(t *testing.T) {

expectedNice := expectedNiceScore(t)
expectedOOM := expectedOOMScore(t)
fmt.Println("expected nice", expectedNice, "expected oom", expectedOOM)
requireOOMScore(t, cmd.Process.Pid, expectedOOM)
requireNiceScore(t, cmd.Process.Pid, expectedNice)
})
Expand All @@ -61,7 +60,8 @@ func requireNiceScore(t *testing.T, pid int, score int) {

nice, err := unix.Getpriority(unix.PRIO_PROCESS, pid)
require.NoError(t, err)
require.Equal(t, score, nice)
// See https://linux.die.net/man/2/setpriority#Notes
require.Equal(t, score, 20-nice)
}

func requireOOMScore(t *testing.T, pid int, expected int) {
Expand Down Expand Up @@ -108,6 +108,10 @@ func cmd(ctx context.Context, t *testing.T, args ...string) (*exec.Cmd, string)
file = filepath.Join(dir, "sentinel")
//nolint:gosec
cmd = exec.CommandContext(ctx, TestBin, "agent-exec", "sh", "-c", fmt.Sprintf("touch %s && sleep 10m", file))
// We set this so we can also easily kill the sleep process the shell spawns.
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
}
cmd.Env = os.Environ()
var buf bytes.Buffer
Expand All @@ -118,10 +122,10 @@ func cmd(ctx context.Context, t *testing.T, args ...string) (*exec.Cmd, string)
if t.Failed() {
t.Logf("cmd %q output: %s", cmd.Args, buf.String())
}

// if cmd.Process != nil {
// _ = cmd.Process.Kill()
// }
if cmd.Process != nil {
// We use -cmd.Process.Pid to kill the whole process group.
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT)
}
})
return cmd, file
}
Expand Down Expand Up @@ -151,7 +155,7 @@ func expectedNiceScore(t *testing.T) int {
require.NoError(t, err)

// Priority is niceness + 20.
score -= 20
score = 20 - score
score += 5
if score > 19 {
return 19
Expand Down
5 changes: 3 additions & 2 deletions agent/agentexec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ const (
// the provided command if CODER_PROC_PRIO_MGMT is set, otherwise a normal exec.Cmd
// is returned. All instances of exec.Cmd should flow through this function to ensure
// proper resource constraints are applied to the child process.
func CommandContext(ctx context.Context, cmd string, args []string) (*exec.Cmd, error) {
_, enabled := envVal(os.Environ(), EnvProcPrioMgmt)
func CommandContext(ctx context.Context, cmd string, args ...string) (*exec.Cmd, error) {
environ := os.Environ()
_, enabled := envVal(environ, EnvProcPrioMgmt)
if runtime.GOOS != "linux" || !enabled {
return exec.CommandContext(ctx, cmd, args...), nil
}
Expand Down
61 changes: 61 additions & 0 deletions agent/agentexec/exec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package agentexec_test

import (
"context"
"os"
"os/exec"
"runtime"
"testing"

"github.com/stretchr/testify/require"

"github.com/coder/coder/v2/agent/agentexec"
)

func TestExec(t *testing.T) {

Check failure on line 15 in agent/agentexec/exec_test.go

View workflow job for this annotation

GitHub Actions / lint

Function TestExec missing the call to method parallel (paralleltest)
t.Run("NonLinux", func(t *testing.T) {

Check failure on line 16 in agent/agentexec/exec_test.go

View workflow job for this annotation

GitHub Actions / lint

empty-lines: extra empty line at the start of a block (revive)

t.Setenv(agentexec.EnvProcPrioMgmt, "true")

if runtime.GOOS == "linux" {
t.Skip("skipping on linux")
}

cmd, err := agentexec.CommandContext(context.Background(), "sh", "-c", "sleep")
require.NoError(t, err)
require.Equal(t, "sh", cmd.Path)
require.Equal(t, []string{"-c", "sleep"}, cmd.Args[1:])
})

t.Run("Linux", func(t *testing.T) {

Check failure on line 30 in agent/agentexec/exec_test.go

View workflow job for this annotation

GitHub Actions / lint

empty-lines: extra empty line at the start of a block (revive)

t.Run("Disabled", func(t *testing.T) {

Check failure on line 32 in agent/agentexec/exec_test.go

View workflow job for this annotation

GitHub Actions / lint

Function TestExec missing the call to method parallel in the test run (paralleltest)
if runtime.GOOS != "linux" {
t.Skip("skipping on linux")
}

cmd, err := agentexec.CommandContext(context.Background(), "sh", "-c", "sleep")
require.NoError(t, err)
path, err := exec.LookPath("sh")
require.NoError(t, err)
require.Equal(t, path, cmd.Path)
require.Equal(t, []string{"sh", "-c", "sleep"}, cmd.Args)
})

t.Run("Enabled", func(t *testing.T) {

Check failure on line 45 in agent/agentexec/exec_test.go

View workflow job for this annotation

GitHub Actions / lint

Function TestExec missing the call to method parallel in the test run (paralleltest)
t.Setenv(agentexec.EnvProcPrioMgmt, "hello")

if runtime.GOOS != "linux" {
t.Skip("skipping on linux")
}

executable, err := os.Executable()
require.NoError(t, err)

cmd, err := agentexec.CommandContext(context.Background(), "sh", "-c", "sleep")
require.NoError(t, err)
require.Equal(t, executable, cmd.Path)
require.Equal(t, []string{executable, "agent-exec", "sh", "-c", "sleep"}, cmd.Args)
})
})
}
2 changes: 0 additions & 2 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,6 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
r.update(),
r.whoami(),

// Hidden
r.agentExec(),
r.expCmd(),
r.gitssh(),
r.support(),
Expand Down
Loading