2018-09-26 17:33:37 +00:00
|
|
|
package drivers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
|
|
|
hclog "github.com/hashicorp/go-hclog"
|
2018-10-04 19:08:20 +00:00
|
|
|
cstructs "github.com/hashicorp/nomad/client/structs"
|
2018-09-26 17:33:37 +00:00
|
|
|
"github.com/hashicorp/nomad/plugins/base"
|
|
|
|
"github.com/hashicorp/nomad/plugins/drivers/proto"
|
|
|
|
"github.com/hashicorp/nomad/plugins/shared/hclspec"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
)
|
|
|
|
|
|
|
|
var _ DriverPlugin = &driverPluginClient{}
|
|
|
|
|
|
|
|
type driverPluginClient struct {
|
2018-09-26 05:18:03 +00:00
|
|
|
*base.BasePluginClient
|
2018-09-26 17:33:37 +00:00
|
|
|
|
|
|
|
client proto.DriverClient
|
|
|
|
logger hclog.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *driverPluginClient) TaskConfigSchema() (*hclspec.Spec, error) {
|
|
|
|
req := &proto.TaskConfigSchemaRequest{}
|
|
|
|
|
|
|
|
resp, err := d.client.TaskConfigSchema(context.Background(), req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp.Spec, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *driverPluginClient) Capabilities() (*Capabilities, error) {
|
|
|
|
req := &proto.CapabilitiesRequest{}
|
|
|
|
|
|
|
|
resp, err := d.client.Capabilities(context.Background(), req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
caps := &Capabilities{}
|
|
|
|
if resp.Capabilities != nil {
|
|
|
|
caps.SendSignals = resp.Capabilities.SendSignals
|
|
|
|
caps.Exec = resp.Capabilities.Exec
|
|
|
|
|
|
|
|
switch resp.Capabilities.FsIsolation {
|
|
|
|
case proto.DriverCapabilities_NONE:
|
2018-10-04 19:08:20 +00:00
|
|
|
caps.FSIsolation = cstructs.FSIsolationNone
|
2018-09-26 17:33:37 +00:00
|
|
|
case proto.DriverCapabilities_CHROOT:
|
2018-10-04 19:08:20 +00:00
|
|
|
caps.FSIsolation = cstructs.FSIsolationChroot
|
2018-09-26 17:33:37 +00:00
|
|
|
case proto.DriverCapabilities_IMAGE:
|
2018-10-04 19:08:20 +00:00
|
|
|
caps.FSIsolation = cstructs.FSIsolationImage
|
2018-09-26 17:33:37 +00:00
|
|
|
default:
|
2018-10-04 19:08:20 +00:00
|
|
|
caps.FSIsolation = cstructs.FSIsolationNone
|
2018-09-26 17:33:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return caps, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fingerprint the driver, return a chan that will be pushed to periodically and on changes to health
|
|
|
|
func (d *driverPluginClient) Fingerprint(ctx context.Context) (<-chan *Fingerprint, error) {
|
|
|
|
req := &proto.FingerprintRequest{}
|
|
|
|
|
2018-10-11 19:42:08 +00:00
|
|
|
stream, err := d.client.Fingerprint(ctx, req)
|
2018-09-26 17:33:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ch := make(chan *Fingerprint)
|
2018-10-11 19:42:08 +00:00
|
|
|
go d.handleFingerprint(ctx, ch, stream)
|
2018-09-26 17:33:37 +00:00
|
|
|
|
|
|
|
return ch, nil
|
|
|
|
}
|
|
|
|
|
2018-10-11 19:42:08 +00:00
|
|
|
func (d *driverPluginClient) handleFingerprint(ctx context.Context, ch chan *Fingerprint, stream proto.Driver_FingerprintClient) {
|
2018-09-26 17:33:37 +00:00
|
|
|
defer close(ch)
|
|
|
|
for {
|
|
|
|
pb, err := stream.Recv()
|
|
|
|
if err == io.EOF {
|
2018-10-11 19:42:08 +00:00
|
|
|
return
|
2018-09-26 17:33:37 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
2018-10-11 19:42:08 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
case ch <- &Fingerprint{Err: fmt.Errorf("error from RPC stream: %v", err)}:
|
2018-10-31 00:13:35 +00:00
|
|
|
d.logger.Error("error receiving stream from Fingerprint driver RPC", "error", err)
|
2018-10-11 19:42:08 +00:00
|
|
|
}
|
|
|
|
return
|
2018-09-26 17:33:37 +00:00
|
|
|
}
|
|
|
|
f := &Fingerprint{
|
|
|
|
Attributes: pb.Attributes,
|
|
|
|
Health: healthStateFromProto(pb.Health),
|
|
|
|
HealthDescription: pb.HealthDescription,
|
|
|
|
}
|
|
|
|
|
2018-10-11 19:42:08 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
case ch <- f:
|
|
|
|
}
|
|
|
|
}
|
2018-09-26 17:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RecoverTask does internal state recovery to be able to control the task of
|
|
|
|
// the given TaskHandle
|
|
|
|
func (d *driverPluginClient) RecoverTask(h *TaskHandle) error {
|
|
|
|
req := &proto.RecoverTaskRequest{Handle: taskHandleToProto(h)}
|
|
|
|
|
|
|
|
_, err := d.client.RecoverTask(context.Background(), req)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartTask starts execution of a task with the given TaskConfig. A TaskHandle
|
|
|
|
// is returned to the caller that can be used to recover state of the task,
|
|
|
|
// should the driver crash or exit prematurely.
|
2018-10-04 19:08:20 +00:00
|
|
|
func (d *driverPluginClient) StartTask(c *TaskConfig) (*TaskHandle, *cstructs.DriverNetwork, error) {
|
2018-09-26 17:33:37 +00:00
|
|
|
req := &proto.StartTaskRequest{
|
|
|
|
Task: taskConfigToProto(c),
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := d.client.StartTask(context.Background(), req)
|
|
|
|
if err != nil {
|
2018-10-04 19:08:20 +00:00
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var net *cstructs.DriverNetwork
|
|
|
|
if resp.NetworkOverride != nil {
|
|
|
|
net = &cstructs.DriverNetwork{
|
|
|
|
PortMap: map[string]int{},
|
|
|
|
IP: resp.NetworkOverride.Addr,
|
|
|
|
AutoAdvertise: resp.NetworkOverride.AutoAdvertise,
|
|
|
|
}
|
|
|
|
for k, v := range resp.NetworkOverride.PortMap {
|
|
|
|
net.PortMap[k] = int(v)
|
|
|
|
}
|
2018-09-26 17:33:37 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 19:08:20 +00:00
|
|
|
return taskHandleFromProto(resp.Handle), net, nil
|
2018-09-26 17:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// WaitTask returns a channel that will have an ExitResult pushed to it once when the task
|
|
|
|
// exits on its own or is killed. If WaitTask is called after the task has exited, the channel
|
|
|
|
// will immedialy return the ExitResult. WaitTask can be called multiple times for
|
|
|
|
// the same task without issue.
|
|
|
|
func (d *driverPluginClient) WaitTask(ctx context.Context, id string) (<-chan *ExitResult, error) {
|
|
|
|
ch := make(chan *ExitResult)
|
|
|
|
go d.handleWaitTask(ctx, id, ch)
|
|
|
|
return ch, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *driverPluginClient) handleWaitTask(ctx context.Context, id string, ch chan *ExitResult) {
|
|
|
|
defer close(ch)
|
|
|
|
var result ExitResult
|
|
|
|
req := &proto.WaitTaskRequest{
|
|
|
|
TaskId: id,
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := d.client.WaitTask(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
result.Err = err
|
|
|
|
} else {
|
|
|
|
result.ExitCode = int(resp.Result.ExitCode)
|
|
|
|
result.Signal = int(resp.Result.Signal)
|
|
|
|
result.OOMKilled = resp.Result.OomKilled
|
|
|
|
if len(resp.Err) > 0 {
|
|
|
|
result.Err = errors.New(resp.Err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ch <- &result
|
|
|
|
}
|
|
|
|
|
|
|
|
// StopTask stops the task with the given taskID. A timeout and signal can be
|
|
|
|
// given to control a graceful termination of the task. The driver will send the
|
|
|
|
// given signal to the task and wait for the given timeout for it to exit. If the
|
|
|
|
// task does not exit within the timeout it will be forcefully killed.
|
|
|
|
func (d *driverPluginClient) StopTask(taskID string, timeout time.Duration, signal string) error {
|
|
|
|
req := &proto.StopTaskRequest{
|
|
|
|
TaskId: taskID,
|
|
|
|
Timeout: ptypes.DurationProto(timeout),
|
|
|
|
Signal: signal,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := d.client.StopTask(context.Background(), req)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// DestroyTask removes the task from the driver's in memory state. The task
|
|
|
|
// cannot be running unless force is set to true. If force is set to true the
|
|
|
|
// driver will forcefully terminate the task before removing it.
|
|
|
|
func (d *driverPluginClient) DestroyTask(taskID string, force bool) error {
|
|
|
|
req := &proto.DestroyTaskRequest{
|
|
|
|
TaskId: taskID,
|
|
|
|
Force: force,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := d.client.DestroyTask(context.Background(), req)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// InspectTask returns status information for a task
|
|
|
|
func (d *driverPluginClient) InspectTask(taskID string) (*TaskStatus, error) {
|
|
|
|
req := &proto.InspectTaskRequest{TaskId: taskID}
|
|
|
|
|
|
|
|
resp, err := d.client.InspectTask(context.Background(), req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
status, err := taskStatusFromProto(resp.Task)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Driver != nil {
|
|
|
|
status.DriverAttributes = resp.Driver.Attributes
|
|
|
|
}
|
|
|
|
if resp.NetworkOverride != nil {
|
2018-10-04 19:08:20 +00:00
|
|
|
status.NetworkOverride = &cstructs.DriverNetwork{
|
|
|
|
PortMap: map[string]int{},
|
|
|
|
IP: resp.NetworkOverride.Addr,
|
2018-09-26 17:33:37 +00:00
|
|
|
AutoAdvertise: resp.NetworkOverride.AutoAdvertise,
|
|
|
|
}
|
2018-10-04 19:08:20 +00:00
|
|
|
for k, v := range resp.NetworkOverride.PortMap {
|
|
|
|
status.NetworkOverride.PortMap[k] = int(v)
|
|
|
|
}
|
2018-09-26 17:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return status, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TaskStats returns resource usage statistics for the task
|
2018-10-04 19:08:20 +00:00
|
|
|
func (d *driverPluginClient) TaskStats(taskID string) (*cstructs.TaskResourceUsage, error) {
|
2018-09-26 17:33:37 +00:00
|
|
|
req := &proto.TaskStatsRequest{TaskId: taskID}
|
|
|
|
|
|
|
|
resp, err := d.client.TaskStats(context.Background(), req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
stats, err := taskStatsFromProto(resp.Stats)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return stats, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TaskEvents returns a channel that will receive events from the driver about all
|
|
|
|
// tasks such as lifecycle events, terminal errors, etc.
|
|
|
|
func (d *driverPluginClient) TaskEvents(ctx context.Context) (<-chan *TaskEvent, error) {
|
|
|
|
req := &proto.TaskEventsRequest{}
|
|
|
|
stream, err := d.client.TaskEvents(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ch := make(chan *TaskEvent)
|
|
|
|
go d.handleTaskEvents(ch, stream)
|
|
|
|
return ch, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *driverPluginClient) handleTaskEvents(ch chan *TaskEvent, stream proto.Driver_TaskEventsClient) {
|
|
|
|
defer close(ch)
|
|
|
|
for {
|
|
|
|
ev, err := stream.Recv()
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
d.logger.Error("error receiving stream from TaskEvents driver RPC", "error", err)
|
|
|
|
ch <- &TaskEvent{Err: err}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
timestamp, _ := ptypes.Timestamp(ev.Timestamp)
|
|
|
|
event := &TaskEvent{
|
|
|
|
TaskID: ev.TaskId,
|
|
|
|
Annotations: ev.Annotations,
|
|
|
|
Message: ev.Message,
|
|
|
|
Timestamp: timestamp,
|
|
|
|
}
|
|
|
|
ch <- event
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SignalTask will send the given signal to the specified task
|
|
|
|
func (d *driverPluginClient) SignalTask(taskID string, signal string) error {
|
|
|
|
req := &proto.SignalTaskRequest{
|
|
|
|
TaskId: taskID,
|
|
|
|
Signal: signal,
|
|
|
|
}
|
|
|
|
_, err := d.client.SignalTask(context.Background(), req)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExecTask will run the given command within the execution context of the task.
|
|
|
|
// The driver will wait for the given timeout for the command to complete before
|
|
|
|
// terminating it. The stdout and stderr of the command will be return to the caller,
|
|
|
|
// along with other exit information such as exit code.
|
|
|
|
func (d *driverPluginClient) ExecTask(taskID string, cmd []string, timeout time.Duration) (*ExecTaskResult, error) {
|
|
|
|
req := &proto.ExecTaskRequest{
|
|
|
|
TaskId: taskID,
|
|
|
|
Command: cmd,
|
|
|
|
Timeout: ptypes.DurationProto(timeout),
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := d.client.ExecTask(context.Background(), req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
result := &ExecTaskResult{
|
|
|
|
Stdout: resp.Stdout,
|
|
|
|
Stderr: resp.Stderr,
|
|
|
|
ExitResult: exitResultFromProto(resp.Result),
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
|
|
}
|