2016-07-10 06:56:31 +00:00
|
|
|
package executor
|
|
|
|
|
|
|
|
import (
|
2018-12-05 16:07:48 +00:00
|
|
|
"context"
|
2018-09-24 18:37:45 +00:00
|
|
|
"fmt"
|
2016-07-10 06:56:31 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
2017-01-26 04:58:24 +00:00
|
|
|
"time"
|
2016-07-10 06:56:31 +00:00
|
|
|
|
2016-12-03 01:04:07 +00:00
|
|
|
"github.com/hashicorp/nomad/client/allocdir"
|
2018-11-30 11:18:39 +00:00
|
|
|
"github.com/hashicorp/nomad/client/taskenv"
|
2016-07-10 06:56:31 +00:00
|
|
|
"github.com/hashicorp/nomad/client/testutil"
|
2018-06-13 22:33:25 +00:00
|
|
|
"github.com/hashicorp/nomad/helper/testlog"
|
2016-08-04 22:03:56 +00:00
|
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
2018-12-07 02:39:53 +00:00
|
|
|
"github.com/hashicorp/nomad/plugins/drivers"
|
2018-09-24 18:37:45 +00:00
|
|
|
tu "github.com/hashicorp/nomad/testutil"
|
2018-12-10 03:30:23 +00:00
|
|
|
lconfigs "github.com/opencontainers/runc/libcontainer/configs"
|
2018-09-24 18:37:45 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2018-12-10 03:30:23 +00:00
|
|
|
"golang.org/x/sys/unix"
|
2016-07-10 06:56:31 +00:00
|
|
|
)
|
|
|
|
|
2018-09-24 18:37:45 +00:00
|
|
|
func init() {
|
|
|
|
executorFactories["LibcontainerExecutor"] = libcontainerFactory
|
|
|
|
}
|
|
|
|
|
2019-03-26 13:06:36 +00:00
|
|
|
var libcontainerFactory = executorFactory{
|
|
|
|
new: NewExecutorWithIsolation,
|
|
|
|
configureExecCmd: func(t *testing.T, cmd *ExecCommand) {
|
|
|
|
cmd.ResourceLimits = true
|
|
|
|
setupRootfs(t, cmd.TaskDir)
|
|
|
|
},
|
2018-09-24 18:37:45 +00:00
|
|
|
}
|
|
|
|
|
2016-12-03 01:04:07 +00:00
|
|
|
// testExecutorContextWithChroot returns an ExecutorContext and AllocDir with
|
|
|
|
// chroot. Use testExecutorContext if you don't need a chroot.
|
|
|
|
//
|
|
|
|
// The caller is responsible for calling AllocDir.Destroy() to cleanup.
|
2019-04-01 15:59:56 +00:00
|
|
|
func testExecutorCommandWithChroot(t *testing.T) *testExecCmd {
|
2016-12-03 01:04:07 +00:00
|
|
|
chrootEnv := map[string]string{
|
|
|
|
"/etc/ld.so.cache": "/etc/ld.so.cache",
|
|
|
|
"/etc/ld.so.conf": "/etc/ld.so.conf",
|
|
|
|
"/etc/ld.so.conf.d": "/etc/ld.so.conf.d",
|
|
|
|
"/lib": "/lib",
|
|
|
|
"/lib64": "/lib64",
|
|
|
|
"/usr/lib": "/usr/lib",
|
|
|
|
"/bin/ls": "/bin/ls",
|
|
|
|
"/bin/echo": "/bin/echo",
|
|
|
|
"/bin/bash": "/bin/bash",
|
2017-01-09 23:40:53 +00:00
|
|
|
"/bin/sleep": "/bin/sleep",
|
2016-12-03 01:04:07 +00:00
|
|
|
"/foobar": "/does/not/exist",
|
|
|
|
}
|
|
|
|
|
|
|
|
alloc := mock.Alloc()
|
|
|
|
task := alloc.Job.TaskGroups[0].Tasks[0]
|
2018-11-30 11:18:39 +00:00
|
|
|
taskEnv := taskenv.NewBuilder(mock.Node(), alloc, task, "global").Build()
|
2016-12-03 01:04:07 +00:00
|
|
|
|
2018-09-24 18:37:45 +00:00
|
|
|
allocDir := allocdir.NewAllocDir(testlog.HCLogger(t), filepath.Join(os.TempDir(), alloc.ID))
|
2016-12-03 01:04:07 +00:00
|
|
|
if err := allocDir.Build(); err != nil {
|
2018-06-13 22:33:25 +00:00
|
|
|
t.Fatalf("AllocDir.Build() failed: %v", err)
|
2016-12-03 01:04:07 +00:00
|
|
|
}
|
2019-01-04 21:11:25 +00:00
|
|
|
if err := allocDir.NewTaskDir(task.Name).Build(true, chrootEnv); err != nil {
|
2016-12-03 01:04:07 +00:00
|
|
|
allocDir.Destroy()
|
2018-06-13 22:33:25 +00:00
|
|
|
t.Fatalf("allocDir.NewTaskDir(%q) failed: %v", task.Name, err)
|
2016-12-03 01:04:07 +00:00
|
|
|
}
|
|
|
|
td := allocDir.TaskDirs[task.Name]
|
2018-12-07 01:54:14 +00:00
|
|
|
cmd := &ExecCommand{
|
2018-09-24 18:37:45 +00:00
|
|
|
Env: taskEnv.List(),
|
2016-12-03 01:04:07 +00:00
|
|
|
TaskDir: td.Dir,
|
2018-12-07 02:39:53 +00:00
|
|
|
Resources: &drivers.Resources{
|
2018-12-14 00:21:41 +00:00
|
|
|
NomadResources: alloc.AllocatedResources.Tasks[task.Name],
|
2018-09-24 18:37:45 +00:00
|
|
|
},
|
2016-08-04 22:03:56 +00:00
|
|
|
}
|
2018-09-24 18:37:45 +00:00
|
|
|
|
2019-04-01 15:59:56 +00:00
|
|
|
testCmd := &testExecCmd{
|
|
|
|
command: cmd,
|
|
|
|
allocDir: allocDir,
|
|
|
|
}
|
|
|
|
configureTLogging(t, testCmd)
|
|
|
|
return testCmd
|
2016-08-04 22:03:56 +00:00
|
|
|
}
|
|
|
|
|
2016-07-10 06:56:31 +00:00
|
|
|
func TestExecutor_IsolationAndConstraints(t *testing.T) {
|
2017-07-21 19:14:54 +00:00
|
|
|
t.Parallel()
|
2018-09-24 18:37:45 +00:00
|
|
|
require := require.New(t)
|
2016-07-10 06:56:31 +00:00
|
|
|
testutil.ExecCompatible(t)
|
|
|
|
|
2019-04-01 15:59:56 +00:00
|
|
|
testExecCmd := testExecutorCommandWithChroot(t)
|
|
|
|
execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
|
2018-09-24 18:37:45 +00:00
|
|
|
execCmd.Cmd = "/bin/ls"
|
|
|
|
execCmd.Args = []string{"-F", "/", "/etc/"}
|
2016-12-03 01:04:07 +00:00
|
|
|
defer allocDir.Destroy()
|
2016-07-10 06:56:31 +00:00
|
|
|
|
|
|
|
execCmd.ResourceLimits = true
|
|
|
|
|
2019-03-26 13:06:36 +00:00
|
|
|
executor := NewExecutorWithIsolation(testlog.HCLogger(t))
|
2018-09-24 18:37:45 +00:00
|
|
|
defer executor.Shutdown("SIGKILL", 0)
|
2016-10-12 18:35:29 +00:00
|
|
|
|
2018-09-24 18:37:45 +00:00
|
|
|
ps, err := executor.Launch(execCmd)
|
|
|
|
require.NoError(err)
|
|
|
|
require.NotZero(ps.Pid)
|
2016-10-12 18:35:29 +00:00
|
|
|
|
2018-12-05 16:07:48 +00:00
|
|
|
state, err := executor.Wait(context.Background())
|
2018-09-24 18:37:45 +00:00
|
|
|
require.NoError(err)
|
|
|
|
require.Zero(state.ExitCode)
|
2016-07-10 06:56:31 +00:00
|
|
|
|
2018-03-11 17:50:28 +00:00
|
|
|
// Check if the resource constraints were applied
|
2018-09-24 18:37:45 +00:00
|
|
|
if lexec, ok := executor.(*LibcontainerExecutor); ok {
|
|
|
|
state, err := lexec.container.State()
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
memLimits := filepath.Join(state.CgroupPaths["memory"], "memory.limit_in_bytes")
|
|
|
|
data, err := ioutil.ReadFile(memLimits)
|
|
|
|
require.NoError(err)
|
|
|
|
|
2018-12-14 00:21:41 +00:00
|
|
|
expectedMemLim := strconv.Itoa(int(execCmd.Resources.NomadResources.Memory.MemoryMB * 1024 * 1024))
|
2018-09-24 18:37:45 +00:00
|
|
|
actualMemLim := strings.TrimSpace(string(data))
|
|
|
|
require.Equal(actualMemLim, expectedMemLim)
|
|
|
|
require.NoError(executor.Shutdown("", 0))
|
2018-12-05 16:07:48 +00:00
|
|
|
executor.Wait(context.Background())
|
2018-09-24 18:37:45 +00:00
|
|
|
|
|
|
|
// Check if Nomad has actually removed the cgroups
|
|
|
|
tu.WaitForResult(func() (bool, error) {
|
|
|
|
_, err = os.Stat(memLimits)
|
|
|
|
if err == nil {
|
|
|
|
return false, fmt.Errorf("expected an error from os.Stat %s", memLimits)
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) { t.Error(err) })
|
2016-07-10 06:56:31 +00:00
|
|
|
|
|
|
|
}
|
2016-09-02 19:44:05 +00:00
|
|
|
expected := `/:
|
|
|
|
alloc/
|
|
|
|
bin/
|
|
|
|
dev/
|
|
|
|
etc/
|
|
|
|
lib/
|
|
|
|
lib64/
|
|
|
|
local/
|
|
|
|
proc/
|
|
|
|
secrets/
|
2018-09-24 18:37:45 +00:00
|
|
|
sys/
|
2016-09-02 19:44:05 +00:00
|
|
|
tmp/
|
|
|
|
usr/
|
|
|
|
|
|
|
|
/etc/:
|
|
|
|
ld.so.cache
|
|
|
|
ld.so.conf
|
|
|
|
ld.so.conf.d/`
|
2018-09-24 18:37:45 +00:00
|
|
|
tu.WaitForResult(func() (bool, error) {
|
2019-04-01 15:59:56 +00:00
|
|
|
output := testExecCmd.stdout.String()
|
2018-09-24 18:37:45 +00:00
|
|
|
act := strings.TrimSpace(string(output))
|
|
|
|
if act != expected {
|
|
|
|
return false, fmt.Errorf("Command output incorrectly: want %v; got %v", expected, act)
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) { t.Error(err) })
|
2016-07-10 06:56:31 +00:00
|
|
|
}
|
2017-01-26 04:58:24 +00:00
|
|
|
|
2019-05-02 17:36:34 +00:00
|
|
|
// Exec Launch looks for the binary only inside the chroot
|
|
|
|
func TestExecutor_EscapeContainer(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
require := require.New(t)
|
|
|
|
testutil.ExecCompatible(t)
|
|
|
|
|
|
|
|
testExecCmd := testExecutorCommandWithChroot(t)
|
|
|
|
execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
|
|
|
|
execCmd.Cmd = "/bin/kill" // missing from the chroot container
|
|
|
|
defer allocDir.Destroy()
|
|
|
|
|
|
|
|
execCmd.ResourceLimits = true
|
|
|
|
|
|
|
|
executor := NewExecutorWithIsolation(testlog.HCLogger(t))
|
|
|
|
defer executor.Shutdown("SIGKILL", 0)
|
|
|
|
|
|
|
|
_, err := executor.Launch(execCmd)
|
|
|
|
require.Error(err)
|
|
|
|
require.Regexp("^file /bin/kill not found under path", err)
|
2019-05-03 20:20:05 +00:00
|
|
|
|
|
|
|
// Bare files are looked up using the system path, inside the container
|
|
|
|
allocDir.Destroy()
|
|
|
|
testExecCmd = testExecutorCommandWithChroot(t)
|
|
|
|
execCmd, allocDir = testExecCmd.command, testExecCmd.allocDir
|
|
|
|
execCmd.Cmd = "kill"
|
|
|
|
_, err = executor.Launch(execCmd)
|
|
|
|
require.Error(err)
|
|
|
|
require.Regexp("^file kill not found under path", err)
|
|
|
|
|
|
|
|
allocDir.Destroy()
|
|
|
|
testExecCmd = testExecutorCommandWithChroot(t)
|
|
|
|
execCmd, allocDir = testExecCmd.command, testExecCmd.allocDir
|
|
|
|
execCmd.Cmd = "echo"
|
|
|
|
_, err = executor.Launch(execCmd)
|
|
|
|
require.NoError(err)
|
2019-05-02 17:36:34 +00:00
|
|
|
}
|
|
|
|
|
2017-01-26 04:58:24 +00:00
|
|
|
func TestExecutor_ClientCleanup(t *testing.T) {
|
2017-07-21 19:14:54 +00:00
|
|
|
t.Parallel()
|
2017-01-26 04:58:24 +00:00
|
|
|
testutil.ExecCompatible(t)
|
2018-09-24 18:37:45 +00:00
|
|
|
require := require.New(t)
|
2017-01-26 04:58:24 +00:00
|
|
|
|
2019-04-01 15:59:56 +00:00
|
|
|
testExecCmd := testExecutorCommandWithChroot(t)
|
|
|
|
execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
|
2017-01-26 04:58:24 +00:00
|
|
|
defer allocDir.Destroy()
|
|
|
|
|
2019-03-26 13:06:36 +00:00
|
|
|
executor := NewExecutorWithIsolation(testlog.HCLogger(t))
|
2018-09-24 18:37:45 +00:00
|
|
|
defer executor.Shutdown("", 0)
|
2017-01-26 04:58:24 +00:00
|
|
|
|
|
|
|
// Need to run a command which will produce continuous output but not
|
|
|
|
// too quickly to ensure executor.Exit() stops the process.
|
2018-09-24 18:37:45 +00:00
|
|
|
execCmd.Cmd = "/bin/bash"
|
|
|
|
execCmd.Args = []string{"-c", "while true; do /bin/echo X; /bin/sleep 1; done"}
|
2017-01-26 04:58:24 +00:00
|
|
|
execCmd.ResourceLimits = true
|
|
|
|
|
2018-09-24 18:37:45 +00:00
|
|
|
ps, err := executor.Launch(execCmd)
|
2018-12-08 23:24:42 +00:00
|
|
|
|
2018-09-24 18:37:45 +00:00
|
|
|
require.NoError(err)
|
|
|
|
require.NotZero(ps.Pid)
|
2017-01-26 04:58:24 +00:00
|
|
|
time.Sleep(500 * time.Millisecond)
|
2018-09-24 18:37:45 +00:00
|
|
|
require.NoError(executor.Shutdown("SIGINT", 100*time.Millisecond))
|
2018-12-08 23:24:42 +00:00
|
|
|
|
|
|
|
ch := make(chan interface{})
|
|
|
|
go func() {
|
2018-12-13 19:41:09 +00:00
|
|
|
executor.Wait(context.Background())
|
2018-12-08 23:24:42 +00:00
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ch:
|
|
|
|
// all good
|
|
|
|
case <-time.After(5 * time.Second):
|
|
|
|
require.Fail("timeout waiting for exec to shutdown")
|
|
|
|
}
|
2017-01-26 04:58:24 +00:00
|
|
|
|
2019-04-01 15:59:56 +00:00
|
|
|
output := testExecCmd.stdout.String()
|
2018-09-24 18:37:45 +00:00
|
|
|
require.NotZero(len(output))
|
2017-01-26 04:58:24 +00:00
|
|
|
time.Sleep(2 * time.Second)
|
2019-04-01 15:59:56 +00:00
|
|
|
output1 := testExecCmd.stdout.String()
|
2018-09-24 18:37:45 +00:00
|
|
|
require.Equal(len(output), len(output1))
|
2017-01-26 04:58:24 +00:00
|
|
|
}
|
2018-12-10 03:30:23 +00:00
|
|
|
|
|
|
|
func TestExecutor_cmdDevices(t *testing.T) {
|
|
|
|
input := []*drivers.DeviceConfig{
|
|
|
|
{
|
|
|
|
HostPath: "/dev/null",
|
|
|
|
TaskPath: "/task/dev/null",
|
|
|
|
Permissions: "rwm",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := &lconfigs.Device{
|
|
|
|
Path: "/task/dev/null",
|
|
|
|
Type: 99,
|
|
|
|
Major: 1,
|
|
|
|
Minor: 3,
|
|
|
|
Permissions: "rwm",
|
|
|
|
}
|
|
|
|
|
|
|
|
found, err := cmdDevices(input)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, found, 1)
|
|
|
|
|
|
|
|
// ignore file permission and ownership
|
|
|
|
// as they are host specific potentially
|
|
|
|
d := found[0]
|
|
|
|
d.FileMode = 0
|
|
|
|
d.Uid = 0
|
|
|
|
d.Gid = 0
|
|
|
|
|
|
|
|
require.EqualValues(t, expected, d)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExecutor_cmdMounts(t *testing.T) {
|
|
|
|
input := []*drivers.MountConfig{
|
|
|
|
{
|
|
|
|
HostPath: "/host/path-ro",
|
|
|
|
TaskPath: "/task/path-ro",
|
|
|
|
Readonly: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
HostPath: "/host/path-rw",
|
|
|
|
TaskPath: "/task/path-rw",
|
|
|
|
Readonly: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := []*lconfigs.Mount{
|
|
|
|
{
|
|
|
|
Source: "/host/path-ro",
|
|
|
|
Destination: "/task/path-ro",
|
|
|
|
Flags: unix.MS_BIND | unix.MS_RDONLY,
|
|
|
|
Device: "bind",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Source: "/host/path-rw",
|
|
|
|
Destination: "/task/path-rw",
|
|
|
|
Flags: unix.MS_BIND,
|
|
|
|
Device: "bind",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
require.EqualValues(t, expected, cmdMounts(input))
|
|
|
|
}
|