Files
nomad/drivers/rawexec/driver_windows_test.go
Tim Gross 6b8ddff1fa windows: set job object for executor and children (#24214)
On Windows, if the `raw_exec` driver's executor exits, the child processes are
not also killed. Create a Windows "job object" (not to be confused with a Nomad
job) and add the executor to it. Child processes of the executor will inherit
the job automatically. When the handle to the job object is freed (on executor
exit), the job itself is destroyed and this causes all processes in that job to
exit.

Fixes: https://github.com/hashicorp/nomad/issues/23668
Ref: https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
2024-10-16 09:20:26 -04:00

97 lines
2.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
//go:build windows
package rawexec
import (
"os"
"testing"
"time"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/plugins/base"
"github.com/hashicorp/nomad/plugins/drivers"
dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils"
"github.com/shoenig/test/must"
)
// TestRawExecDriver_ExecutorKill verifies that killing the executor will stop
// its child processes
func TestRawExecDriver_ExecutorKill(t *testing.T) {
ci.Parallel(t)
d := newEnabledRawExecDriver(t)
harness := dtestutil.NewDriverHarness(t, d)
t.Cleanup(harness.Kill)
config := &Config{Enabled: true}
var data []byte
must.NoError(t, base.MsgPackEncode(&data, config))
bconfig := &base.Config{
PluginConfig: data,
AgentConfig: &base.AgentConfig{
Driver: &base.ClientDriverConfig{
Topology: d.nomadConfig.Topology,
},
},
}
must.NoError(t, harness.SetConfig(bconfig))
allocID := uuid.Generate()
taskName := "test"
task := &drivers.TaskConfig{
AllocID: allocID,
ID: uuid.Generate(),
Name: taskName,
Resources: testResources(allocID, taskName),
}
taskConfig := map[string]interface{}{}
taskConfig["command"] = "Powershell.exe"
taskConfig["args"] = []string{"sleep", "100s"}
must.NoError(t, task.EncodeConcreteDriverConfig(&taskConfig))
cleanup := harness.MkAllocDir(task, false)
t.Cleanup(cleanup)
handle, _, err := harness.StartTask(task)
must.NoError(t, err)
var taskState TaskState
must.NoError(t, handle.GetDriverState(&taskState))
must.NoError(t, harness.WaitUntilStarted(task.ID, 1*time.Second))
// forcibly kill the executor, not the workload
must.NotEq(t, taskState.ReattachConfig.Pid, taskState.Pid)
proc, err := os.FindProcess(taskState.ReattachConfig.Pid)
must.NoError(t, err)
taskProc, err := os.FindProcess(taskState.Pid)
must.NoError(t, err)
must.NoError(t, proc.Kill())
t.Logf("killed %d, waiting on %d to stop", taskState.ReattachConfig.Pid, taskState.Pid)
t.Cleanup(func() {
if taskProc != nil {
taskProc.Kill()
}
})
done := make(chan struct{})
go func() {
taskProc.Wait()
close(done)
}()
select {
case <-time.After(5 * time.Second):
t.Fatal("expected child process to exit")
case <-done:
}
}