Executor + Java/Raw Exec/Exec
This commit is contained in:
parent
5b01b1be1b
commit
00a1234c55
|
@ -260,7 +260,7 @@ func (h *execHandle) Update(task *structs.Task) error {
|
|||
}
|
||||
|
||||
func (h *execHandle) Signal(s os.Signal) error {
|
||||
return nil
|
||||
return h.executor.Signal(s)
|
||||
}
|
||||
|
||||
func (h *execHandle) Kill() error {
|
||||
|
|
|
@ -292,6 +292,81 @@ func TestExecDriver_Start_Kill_Wait(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestExecDriver_Signal(t *testing.T) {
|
||||
ctestutils.ExecCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "signal",
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/bash",
|
||||
"args": []string{"test.sh"},
|
||||
},
|
||||
LogConfig: &structs.LogConfig{
|
||||
MaxFiles: 10,
|
||||
MaxFileSizeMB: 10,
|
||||
},
|
||||
Resources: basicResources,
|
||||
KillTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
driverCtx, execCtx := testDriverContexts(task)
|
||||
defer execCtx.AllocDir.Destroy()
|
||||
d := NewExecDriver(driverCtx)
|
||||
|
||||
testFile := filepath.Join(execCtx.AllocDir.TaskDirs["signal"], "test.sh")
|
||||
testData := []byte(`
|
||||
at_term() {
|
||||
echo 'Terminated.'
|
||||
exit 3
|
||||
}
|
||||
trap at_term USR1
|
||||
while true; do
|
||||
sleep 1
|
||||
done
|
||||
`)
|
||||
if err := ioutil.WriteFile(testFile, testData, 0777); err != nil {
|
||||
fmt.Errorf("Failed to write data")
|
||||
}
|
||||
|
||||
handle, err := d.Start(execCtx, task)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if handle == nil {
|
||||
t.Fatalf("missing handle")
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
err := handle.Signal(syscall.SIGUSR1)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Task should terminate quickly
|
||||
select {
|
||||
case res := <-handle.WaitCh():
|
||||
if res.Successful() {
|
||||
t.Fatal("should err")
|
||||
}
|
||||
case <-time.After(time.Duration(testutil.TestMultiplier()*6) * time.Second):
|
||||
t.Fatalf("timeout")
|
||||
}
|
||||
|
||||
// Check the log file to see it exited because of the signal
|
||||
outputFile := filepath.Join(execCtx.AllocDir.LogDir(), "signal.stdout.0")
|
||||
act, err := ioutil.ReadFile(outputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read expected output: %v", err)
|
||||
}
|
||||
|
||||
exp := "Terminated."
|
||||
if strings.TrimSpace(string(act)) != exp {
|
||||
t.Logf("Read from %v", outputFile)
|
||||
t.Fatalf("Command outputted %v; want %v", act, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecDriverUser(t *testing.T) {
|
||||
ctestutils.ExecCompatible(t)
|
||||
task := &structs.Task{
|
||||
|
|
|
@ -59,6 +59,7 @@ type Executor interface {
|
|||
DeregisterServices() error
|
||||
Version() (*ExecutorVersion, error)
|
||||
Stats() (*cstructs.TaskResourceUsage, error)
|
||||
Signal(s os.Signal) error
|
||||
}
|
||||
|
||||
// ConsulContext holds context to configure the Consul client and run checks
|
||||
|
@ -396,6 +397,10 @@ func (e *UniversalExecutor) wait() {
|
|||
e.exitState = &ProcessState{Pid: 0, ExitCode: 0, IsolationConfig: ic, Time: time.Now()}
|
||||
return
|
||||
}
|
||||
|
||||
e.lre.Close()
|
||||
e.lro.Close()
|
||||
|
||||
exitCode := 1
|
||||
var signal int
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
|
@ -856,3 +861,19 @@ func (e *UniversalExecutor) aggregatedResourceUsage(pidStats map[string]*cstruct
|
|||
Pids: pidStats,
|
||||
}
|
||||
}
|
||||
|
||||
// Signal sends the passed signal to the task
|
||||
func (e *UniversalExecutor) Signal(s os.Signal) error {
|
||||
if e.cmd.Process == nil {
|
||||
return fmt.Errorf("Task not yet run")
|
||||
}
|
||||
|
||||
e.logger.Printf("[DEBUG] executor: sending signal %s", s)
|
||||
err := e.cmd.Process.Signal(s)
|
||||
if err != nil {
|
||||
e.logger.Printf("[ERR] executor: sending signal %s failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/gob"
|
||||
"log"
|
||||
"net/rpc"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/client/driver/executor"
|
||||
|
@ -18,6 +20,8 @@ func init() {
|
|||
gob.Register(map[string]interface{}{})
|
||||
gob.Register([]map[string]string{})
|
||||
gob.Register([]map[string]int{})
|
||||
gob.Register(new(os.Signal))
|
||||
gob.Register(syscall.Signal(0x1))
|
||||
}
|
||||
|
||||
type ExecutorRPC struct {
|
||||
|
@ -95,6 +99,10 @@ func (e *ExecutorRPC) Stats() (*cstructs.TaskResourceUsage, error) {
|
|||
return &resourceUsage, err
|
||||
}
|
||||
|
||||
func (e *ExecutorRPC) Signal(s os.Signal) error {
|
||||
return e.client.Call("Plugin.Signal", &s, new(interface{}))
|
||||
}
|
||||
|
||||
type ExecutorRPCServer struct {
|
||||
Impl executor.Executor
|
||||
logger *log.Logger
|
||||
|
@ -164,6 +172,10 @@ func (e *ExecutorRPCServer) Stats(args interface{}, resourceUsage *cstructs.Task
|
|||
return err
|
||||
}
|
||||
|
||||
func (e *ExecutorRPCServer) Signal(args os.Signal, resp *interface{}) error {
|
||||
return e.Impl.Signal(args)
|
||||
}
|
||||
|
||||
type ExecutorPlugin struct {
|
||||
logger *log.Logger
|
||||
Impl *ExecutorRPCServer
|
||||
|
|
|
@ -360,7 +360,7 @@ func (h *javaHandle) Update(task *structs.Task) error {
|
|||
}
|
||||
|
||||
func (h *javaHandle) Signal(s os.Signal) error {
|
||||
return nil
|
||||
return h.executor.Signal(s)
|
||||
}
|
||||
|
||||
func (h *javaHandle) Kill() error {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -235,6 +236,64 @@ func TestJavaDriver_Start_Kill_Wait(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestJavaDriver_Signal(t *testing.T) {
|
||||
if !javaLocated() {
|
||||
t.Skip("Java not found; skipping")
|
||||
}
|
||||
|
||||
ctestutils.JavaCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "demo-app",
|
||||
Config: map[string]interface{}{
|
||||
"jar_path": "demoapp.jar",
|
||||
},
|
||||
LogConfig: &structs.LogConfig{
|
||||
MaxFiles: 10,
|
||||
MaxFileSizeMB: 10,
|
||||
},
|
||||
Resources: basicResources,
|
||||
}
|
||||
|
||||
driverCtx, execCtx := testDriverContexts(task)
|
||||
defer execCtx.AllocDir.Destroy()
|
||||
d := NewJavaDriver(driverCtx)
|
||||
|
||||
// Copy the test jar into the task's directory
|
||||
dst, _ := execCtx.AllocDir.TaskDirs[task.Name]
|
||||
copyFile("./test-resources/java/demoapp.jar", filepath.Join(dst, "demoapp.jar"), t)
|
||||
|
||||
handle, err := d.Start(execCtx, task)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if handle == nil {
|
||||
t.Fatalf("missing handle")
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
err := handle.Signal(syscall.SIGHUP)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Task should terminate quickly
|
||||
select {
|
||||
case res := <-handle.WaitCh():
|
||||
if res.Successful() {
|
||||
t.Fatal("should err")
|
||||
}
|
||||
case <-time.After(time.Duration(testutil.TestMultiplier()*10) * time.Second):
|
||||
t.Fatalf("timeout")
|
||||
|
||||
// Need to kill long lived process
|
||||
if err = handle.Kill(); err != nil {
|
||||
t.Fatalf("Error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJavaDriverUser(t *testing.T) {
|
||||
if !javaLocated() {
|
||||
t.Skip("Java not found; skipping")
|
||||
|
|
|
@ -257,7 +257,7 @@ func (h *rawExecHandle) Update(task *structs.Task) error {
|
|||
}
|
||||
|
||||
func (h *rawExecHandle) Signal(s os.Signal) error {
|
||||
return nil
|
||||
return h.executor.Signal(s)
|
||||
}
|
||||
|
||||
func (h *rawExecHandle) Kill() error {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -277,3 +278,77 @@ func TestRawExecDriverUser(t *testing.T) {
|
|||
t.Fatalf("Expecting '%v' in '%v'", msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawExecDriver_Signal(t *testing.T) {
|
||||
task := &structs.Task{
|
||||
Name: "signal",
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/bash",
|
||||
"args": []string{"test.sh"},
|
||||
},
|
||||
LogConfig: &structs.LogConfig{
|
||||
MaxFiles: 10,
|
||||
MaxFileSizeMB: 10,
|
||||
},
|
||||
Resources: basicResources,
|
||||
KillTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
driverCtx, execCtx := testDriverContexts(task)
|
||||
defer execCtx.AllocDir.Destroy()
|
||||
d := NewExecDriver(driverCtx)
|
||||
|
||||
testFile := filepath.Join(execCtx.AllocDir.TaskDirs["signal"], "test.sh")
|
||||
testData := []byte(`
|
||||
at_term() {
|
||||
echo 'Terminated.'
|
||||
exit 3
|
||||
}
|
||||
trap at_term USR1
|
||||
while true; do
|
||||
sleep 1
|
||||
done
|
||||
`)
|
||||
if err := ioutil.WriteFile(testFile, testData, 0777); err != nil {
|
||||
fmt.Errorf("Failed to write data")
|
||||
}
|
||||
|
||||
handle, err := d.Start(execCtx, task)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if handle == nil {
|
||||
t.Fatalf("missing handle")
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
err := handle.Signal(syscall.SIGUSR1)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Task should terminate quickly
|
||||
select {
|
||||
case res := <-handle.WaitCh():
|
||||
if res.Successful() {
|
||||
t.Fatal("should err")
|
||||
}
|
||||
case <-time.After(time.Duration(testutil.TestMultiplier()*6) * time.Second):
|
||||
t.Fatalf("timeout")
|
||||
}
|
||||
|
||||
// Check the log file to see it exited because of the signal
|
||||
outputFile := filepath.Join(execCtx.AllocDir.LogDir(), "signal.stdout.0")
|
||||
act, err := ioutil.ReadFile(outputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read expected output: %v", err)
|
||||
}
|
||||
|
||||
exp := "Terminated."
|
||||
if strings.TrimSpace(string(act)) != exp {
|
||||
t.Logf("Read from %v", outputFile)
|
||||
t.Fatalf("Command outputted %v; want %v", act, exp)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,4 +9,4 @@ public class Hello {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue