Executor + Java/Raw Exec/Exec

This commit is contained in:
Alex Dadgar 2016-10-10 11:46:27 -07:00
parent 5b01b1be1b
commit 00a1234c55
9 changed files with 246 additions and 4 deletions

View file

@ -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 {

View file

@ -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{

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -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")

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -9,4 +9,4 @@ public class Hello {
}
}
}
}
}