Skip to content
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

Driver start recoverable #1891

Merged
merged 2 commits into from
Oct 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/driver/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle
}

if err := d.createImage(driverConfig, client, taskDir); err != nil {
return nil, fmt.Errorf("failed to create image: %v", err)
return nil, err
}

image := driverConfig.ImageName
Expand Down
40 changes: 40 additions & 0 deletions client/driver/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,46 @@ func TestDockerDriver_Start_LoadImage(t *testing.T) {

}

func TestDockerDriver_Start_BadPull_Recoverable(t *testing.T) {
if !testutil.DockerIsConnected(t) {
t.SkipNow()
}
task := &structs.Task{
Name: "busybox-demo",
Config: map[string]interface{}{
"image": "127.0.1.1:32121/foo", // bad path
"command": "/bin/echo",
"args": []string{
"hello",
},
},
LogConfig: &structs.LogConfig{
MaxFiles: 10,
MaxFileSizeMB: 10,
},
Resources: &structs.Resources{
MemoryMB: 256,
CPU: 512,
},
}

driverCtx, execCtx := testDriverContexts(task)
driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
defer execCtx.AllocDir.Destroy()
d := NewDockerDriver(driverCtx)

_, err := d.Start(execCtx, task)
if err == nil {
t.Fatalf("want err: %v", err)
}

if rerr, ok := err.(*structs.RecoverableError); !ok {
t.Fatalf("want recoverable error: %+v", err)
} else if !rerr.Recoverable {
t.Fatalf("error not recoverable: %+v", err)
}
}

func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) {
// This test requires that the alloc dir be mounted into docker as a volume.
// Because this cannot happen when docker is run remotely, e.g. when running
Expand Down
11 changes: 11 additions & 0 deletions client/driver/mock_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ func init() {
// MockDriverConfig is the driver configuration for the MockDriver
type MockDriverConfig struct {

// StartErr specifies the error that should be returned when starting the
// mock driver.
StartErr string `mapstructure:"start_error"`

// StartErrRecoverable marks the error returned is recoverable
StartErrRecoverable bool `mapstructure:"start_error_recoverable"`

// KillAfter is the duration after which the mock driver indicates the task
// has exited after getting the initial SIGINT signal
KillAfter time.Duration `mapstructure:"kill_after"`
Expand Down Expand Up @@ -83,6 +90,10 @@ func (m *MockDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
return nil, err
}

if driverConfig.StartErr != "" {
return nil, structs.NewRecoverableError(errors.New(driverConfig.StartErr), driverConfig.StartErrRecoverable)
}

h := mockDriverHandle{
taskName: task.Name,
runFor: driverConfig.RunFor,
Expand Down
11 changes: 10 additions & 1 deletion client/task_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -1018,8 +1018,17 @@ func (r *TaskRunner) startTask() error {
// Start the job
handle, err := driver.Start(r.ctx, r.task)
if err != nil {
return fmt.Errorf("failed to start task '%s' for alloc '%s': %v",
wrapped := fmt.Errorf("failed to start task '%s' for alloc '%s': %v",
r.task.Name, r.alloc.ID, err)

r.logger.Printf("[INFO] client: %v", wrapped)

if rerr, ok := err.(*structs.RecoverableError); ok {
return structs.NewRecoverableError(wrapped, rerr.Recoverable)
}

return wrapped

}

r.handleLock.Lock()
Expand Down
39 changes: 39 additions & 0 deletions client/task_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,45 @@ func TestTaskRunner_SimpleRun(t *testing.T) {
}
}

func TestTaskRunner_Run_RecoverableStartError(t *testing.T) {
alloc := mock.Alloc()
task := alloc.Job.TaskGroups[0].Tasks[0]
task.Driver = "mock_driver"
task.Config = map[string]interface{}{
"exit_code": 0,
"start_error": "driver failure",
"start_error_recoverable": true,
}

upd, tr := testTaskRunnerFromAlloc(true, alloc)
tr.MarkReceived()
go tr.Run()
defer tr.Destroy(structs.NewTaskEvent(structs.TaskKilled))
defer tr.ctx.AllocDir.Destroy()

testutil.WaitForResult(func() (bool, error) {
if l := len(upd.events); l < 3 {
return false, fmt.Errorf("Expect at least three events; got %v", l)
}

if upd.events[0].Type != structs.TaskReceived {
return false, fmt.Errorf("First Event was %v; want %v", upd.events[0].Type, structs.TaskReceived)
}

if upd.events[1].Type != structs.TaskDriverFailure {
return false, fmt.Errorf("Second Event was %v; want %v", upd.events[1].Type, structs.TaskDriverFailure)
}

if upd.events[2].Type != structs.TaskRestarting {
return false, fmt.Errorf("Second Event was %v; want %v", upd.events[2].Type, structs.TaskRestarting)
}

return true, nil
}, func(err error) {
t.Fatalf("err: %v", err)
})
}

func TestTaskRunner_Destroy(t *testing.T) {
ctestutil.ExecCompatible(t)
upd, tr := testTaskRunner(true)
Expand Down