github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/drivers/java/driver.go (about)

     1  package java
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"runtime"
    10  	"time"
    11  
    12  	"github.com/hashicorp/consul-template/signals"
    13  	hclog "github.com/hashicorp/go-hclog"
    14  	"github.com/hashicorp/nomad/client/fingerprint"
    15  	"github.com/hashicorp/nomad/drivers/shared/eventer"
    16  	"github.com/hashicorp/nomad/drivers/shared/executor"
    17  	"github.com/hashicorp/nomad/drivers/shared/resolvconf"
    18  	"github.com/hashicorp/nomad/helper/pluginutils/loader"
    19  	"github.com/hashicorp/nomad/plugins/base"
    20  	"github.com/hashicorp/nomad/plugins/drivers"
    21  	"github.com/hashicorp/nomad/plugins/drivers/utils"
    22  	"github.com/hashicorp/nomad/plugins/shared/hclspec"
    23  	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
    24  )
    25  
    26  const (
    27  	// pluginName is the name of the plugin
    28  	pluginName = "java"
    29  
    30  	// fingerprintPeriod is the interval at which the driver will send fingerprint responses
    31  	fingerprintPeriod = 30 * time.Second
    32  
    33  	// The key populated in Node Attributes to indicate presence of the Java driver
    34  	driverAttr        = "driver.java"
    35  	driverVersionAttr = "driver.java.version"
    36  
    37  	// taskHandleVersion is the version of task handle which this driver sets
    38  	// and understands how to decode driver state
    39  	taskHandleVersion = 1
    40  )
    41  
    42  var (
    43  	// PluginID is the java plugin metadata registered in the plugin
    44  	// catalog.
    45  	PluginID = loader.PluginID{
    46  		Name:       pluginName,
    47  		PluginType: base.PluginTypeDriver,
    48  	}
    49  
    50  	// PluginConfig is the java driver factory function registered in the
    51  	// plugin catalog.
    52  	PluginConfig = &loader.InternalPluginConfig{
    53  		Config:  map[string]interface{}{},
    54  		Factory: func(ctx context.Context, l hclog.Logger) interface{} { return NewDriver(ctx, l) },
    55  	}
    56  
    57  	// pluginInfo is the response returned for the PluginInfo RPC
    58  	pluginInfo = &base.PluginInfoResponse{
    59  		Type:              base.PluginTypeDriver,
    60  		PluginApiVersions: []string{drivers.ApiVersion010},
    61  		PluginVersion:     "0.1.0",
    62  		Name:              pluginName,
    63  	}
    64  
    65  	// configSpec is the hcl specification returned by the ConfigSchema RPC
    66  	configSpec = hclspec.NewObject(map[string]*hclspec.Spec{
    67  		"default_pid_mode": hclspec.NewDefault(
    68  			hclspec.NewAttr("default_pid_mode", "string", false),
    69  			hclspec.NewLiteral(`"private"`),
    70  		),
    71  		"default_ipc_mode": hclspec.NewDefault(
    72  			hclspec.NewAttr("default_ipc_mode", "string", false),
    73  			hclspec.NewLiteral(`"private"`),
    74  		),
    75  	})
    76  
    77  	// taskConfigSpec is the hcl specification for the driver config section of
    78  	// a taskConfig within a job. It is returned in the TaskConfigSchema RPC
    79  	taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{
    80  		// It's required for either `class` or `jar_path` to be set,
    81  		// but that's not expressable in hclspec.  Marking both as optional
    82  		// and setting checking explicitly later
    83  		"class":       hclspec.NewAttr("class", "string", false),
    84  		"class_path":  hclspec.NewAttr("class_path", "string", false),
    85  		"jar_path":    hclspec.NewAttr("jar_path", "string", false),
    86  		"jvm_options": hclspec.NewAttr("jvm_options", "list(string)", false),
    87  		"args":        hclspec.NewAttr("args", "list(string)", false),
    88  		"pid_mode":    hclspec.NewAttr("pid_mode", "string", false),
    89  		"ipc_mode":    hclspec.NewAttr("ipc_mode", "string", false),
    90  	})
    91  
    92  	// capabilities is returned by the Capabilities RPC and indicates what
    93  	// optional features this driver supports
    94  	capabilities = &drivers.Capabilities{
    95  		SendSignals: false,
    96  		Exec:        false,
    97  		FSIsolation: drivers.FSIsolationNone,
    98  		NetIsolationModes: []drivers.NetIsolationMode{
    99  			drivers.NetIsolationModeHost,
   100  			drivers.NetIsolationModeGroup,
   101  		},
   102  		MountConfigs: drivers.MountConfigSupportNone,
   103  	}
   104  
   105  	_ drivers.DriverPlugin = (*Driver)(nil)
   106  )
   107  
   108  func init() {
   109  	if runtime.GOOS == "linux" {
   110  		capabilities.FSIsolation = drivers.FSIsolationChroot
   111  		capabilities.MountConfigs = drivers.MountConfigSupportAll
   112  	}
   113  }
   114  
   115  // Config is the driver configuration set by the SetConfig RPC call
   116  type Config struct {
   117  	// DefaultModePID is the default PID isolation set for all tasks using
   118  	// exec-based task drivers.
   119  	DefaultModePID string `codec:"default_pid_mode"`
   120  
   121  	// DefaultModeIPC is the default IPC isolation set for all tasks using
   122  	// exec-based task drivers.
   123  	DefaultModeIPC string `codec:"default_ipc_mode"`
   124  }
   125  
   126  func (c *Config) validate() error {
   127  	switch c.DefaultModePID {
   128  	case executor.IsolationModePrivate, executor.IsolationModeHost:
   129  	default:
   130  		return fmt.Errorf("default_pid_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, c.DefaultModePID)
   131  	}
   132  
   133  	switch c.DefaultModeIPC {
   134  	case executor.IsolationModePrivate, executor.IsolationModeHost:
   135  	default:
   136  		return fmt.Errorf("default_ipc_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, c.DefaultModeIPC)
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  // TaskConfig is the driver configuration of a taskConfig within a job
   143  type TaskConfig struct {
   144  	Class     string   `codec:"class"`
   145  	ClassPath string   `codec:"class_path"`
   146  	JarPath   string   `codec:"jar_path"`
   147  	JvmOpts   []string `codec:"jvm_options"`
   148  	Args      []string `codec:"args"` // extra arguments to java executable
   149  	ModePID   string   `codec:"pid_mode"`
   150  	ModeIPC   string   `codec:"ipc_mode"`
   151  }
   152  
   153  func (tc *TaskConfig) validate() error {
   154  	switch tc.ModePID {
   155  	case "", executor.IsolationModePrivate, executor.IsolationModeHost:
   156  	default:
   157  		return fmt.Errorf("pid_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, tc.ModePID)
   158  
   159  	}
   160  
   161  	switch tc.ModeIPC {
   162  	case "", executor.IsolationModePrivate, executor.IsolationModeHost:
   163  	default:
   164  		return fmt.Errorf("ipc_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, tc.ModeIPC)
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  // TaskState is the state which is encoded in the handle returned in
   171  // StartTask. This information is needed to rebuild the taskConfig state and handler
   172  // during recovery.
   173  type TaskState struct {
   174  	ReattachConfig *pstructs.ReattachConfig
   175  	TaskConfig     *drivers.TaskConfig
   176  	Pid            int
   177  	StartedAt      time.Time
   178  }
   179  
   180  // Driver is a driver for running images via Java
   181  type Driver struct {
   182  	// eventer is used to handle multiplexing of TaskEvents calls such that an
   183  	// event can be broadcast to all callers
   184  	eventer *eventer.Eventer
   185  
   186  	// config is the driver configuration set by the SetConfig RPC
   187  	config Config
   188  
   189  	// tasks is the in memory datastore mapping taskIDs to taskHandle
   190  	tasks *taskStore
   191  
   192  	// ctx is the context for the driver. It is passed to other subsystems to
   193  	// coordinate shutdown
   194  	ctx context.Context
   195  
   196  	// nomadConf is the client agent's configuration
   197  	nomadConfig *base.ClientDriverConfig
   198  
   199  	// logger will log to the Nomad agent
   200  	logger hclog.Logger
   201  }
   202  
   203  func NewDriver(ctx context.Context, logger hclog.Logger) drivers.DriverPlugin {
   204  	logger = logger.Named(pluginName)
   205  	return &Driver{
   206  		eventer: eventer.NewEventer(ctx, logger),
   207  		tasks:   newTaskStore(),
   208  		ctx:     ctx,
   209  		logger:  logger,
   210  	}
   211  }
   212  
   213  func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) {
   214  	return pluginInfo, nil
   215  }
   216  
   217  func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
   218  	return configSpec, nil
   219  }
   220  
   221  func (d *Driver) SetConfig(cfg *base.Config) error {
   222  	// unpack, validate, and set agent plugin config
   223  	var config Config
   224  	if len(cfg.PluginConfig) != 0 {
   225  		if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil {
   226  			return err
   227  		}
   228  	}
   229  	if err := config.validate(); err != nil {
   230  		return err
   231  	}
   232  	d.config = config
   233  
   234  	if cfg != nil && cfg.AgentConfig != nil {
   235  		d.nomadConfig = cfg.AgentConfig.Driver
   236  	}
   237  	return nil
   238  }
   239  
   240  func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) {
   241  	return taskConfigSpec, nil
   242  }
   243  
   244  func (d *Driver) Capabilities() (*drivers.Capabilities, error) {
   245  	return capabilities, nil
   246  }
   247  
   248  func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) {
   249  	ch := make(chan *drivers.Fingerprint)
   250  	go d.handleFingerprint(ctx, ch)
   251  	return ch, nil
   252  }
   253  
   254  func (d *Driver) handleFingerprint(ctx context.Context, ch chan *drivers.Fingerprint) {
   255  	ticker := time.NewTimer(0)
   256  	for {
   257  		select {
   258  		case <-ctx.Done():
   259  			return
   260  		case <-d.ctx.Done():
   261  			return
   262  		case <-ticker.C:
   263  			ticker.Reset(fingerprintPeriod)
   264  			ch <- d.buildFingerprint()
   265  		}
   266  	}
   267  }
   268  
   269  func (d *Driver) buildFingerprint() *drivers.Fingerprint {
   270  	fp := &drivers.Fingerprint{
   271  		Attributes:        map[string]*pstructs.Attribute{},
   272  		Health:            drivers.HealthStateHealthy,
   273  		HealthDescription: drivers.DriverHealthy,
   274  	}
   275  
   276  	if runtime.GOOS == "linux" {
   277  		// Only enable if w are root and cgroups are mounted when running on linux system
   278  		if !utils.IsUnixRoot() {
   279  			fp.Health = drivers.HealthStateUndetected
   280  			fp.HealthDescription = drivers.DriverRequiresRootMessage
   281  			return fp
   282  		}
   283  
   284  		mount, err := fingerprint.FindCgroupMountpointDir()
   285  		if err != nil {
   286  			fp.Health = drivers.HealthStateUnhealthy
   287  			fp.HealthDescription = drivers.NoCgroupMountMessage
   288  			d.logger.Warn(fp.HealthDescription, "error", err)
   289  			return fp
   290  		}
   291  
   292  		if mount == "" {
   293  			fp.Health = drivers.HealthStateUnhealthy
   294  			fp.HealthDescription = drivers.CgroupMountEmpty
   295  			return fp
   296  		}
   297  	}
   298  	if runtime.GOOS == "darwin" {
   299  		_, err := checkForMacJVM()
   300  		if err != nil {
   301  			// return no error, as it isn't an error to not find java, it just means we
   302  			// can't use it.
   303  
   304  			fp.Health = drivers.HealthStateUndetected
   305  			fp.HealthDescription = ""
   306  			d.logger.Trace("macOS jvm not found", "error", err)
   307  			return fp
   308  		}
   309  	}
   310  
   311  	version, runtime, vm, err := javaVersionInfo()
   312  	if err != nil {
   313  		// return no error, as it isn't an error to not find java, it just means we
   314  		// can't use it.
   315  		fp.Health = drivers.HealthStateUndetected
   316  		fp.HealthDescription = ""
   317  		return fp
   318  	}
   319  
   320  	fp.Attributes[driverAttr] = pstructs.NewBoolAttribute(true)
   321  	fp.Attributes[driverVersionAttr] = pstructs.NewStringAttribute(version)
   322  	fp.Attributes["driver.java.runtime"] = pstructs.NewStringAttribute(runtime)
   323  	fp.Attributes["driver.java.vm"] = pstructs.NewStringAttribute(vm)
   324  
   325  	return fp
   326  }
   327  
   328  func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error {
   329  	if handle == nil {
   330  		return fmt.Errorf("handle cannot be nil")
   331  	}
   332  
   333  	// COMPAT(0.10): pre 0.9 upgrade path check
   334  	if handle.Version == 0 {
   335  		return d.recoverPre09Task(handle)
   336  	}
   337  
   338  	// If already attached to handle there's nothing to recover.
   339  	if _, ok := d.tasks.Get(handle.Config.ID); ok {
   340  		d.logger.Debug("nothing to recover; task already exists",
   341  			"task_id", handle.Config.ID,
   342  			"task_name", handle.Config.Name,
   343  		)
   344  		return nil
   345  	}
   346  
   347  	var taskState TaskState
   348  	if err := handle.GetDriverState(&taskState); err != nil {
   349  		d.logger.Error("failed to decode taskConfig state from handle", "error", err, "task_id", handle.Config.ID)
   350  		return fmt.Errorf("failed to decode taskConfig state from handle: %v", err)
   351  	}
   352  
   353  	plugRC, err := pstructs.ReattachConfigToGoPlugin(taskState.ReattachConfig)
   354  	if err != nil {
   355  		d.logger.Error("failed to build ReattachConfig from taskConfig state", "error", err, "task_id", handle.Config.ID)
   356  		return fmt.Errorf("failed to build ReattachConfig from taskConfig state: %v", err)
   357  	}
   358  
   359  	execImpl, pluginClient, err := executor.ReattachToExecutor(plugRC,
   360  		d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID))
   361  	if err != nil {
   362  		d.logger.Error("failed to reattach to executor", "error", err, "task_id", handle.Config.ID)
   363  		return fmt.Errorf("failed to reattach to executor: %v", err)
   364  	}
   365  
   366  	h := &taskHandle{
   367  		exec:         execImpl,
   368  		pid:          taskState.Pid,
   369  		pluginClient: pluginClient,
   370  		taskConfig:   taskState.TaskConfig,
   371  		procState:    drivers.TaskStateRunning,
   372  		startedAt:    taskState.StartedAt,
   373  		exitResult:   &drivers.ExitResult{},
   374  		logger:       d.logger,
   375  	}
   376  
   377  	d.tasks.Set(taskState.TaskConfig.ID, h)
   378  
   379  	go h.run()
   380  	return nil
   381  }
   382  
   383  func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
   384  	if _, ok := d.tasks.Get(cfg.ID); ok {
   385  		return nil, nil, fmt.Errorf("task with ID %q already started", cfg.ID)
   386  	}
   387  
   388  	var driverConfig TaskConfig
   389  	if err := cfg.DecodeDriverConfig(&driverConfig); err != nil {
   390  		return nil, nil, fmt.Errorf("failed to decode driver config: %v", err)
   391  	}
   392  
   393  	if err := driverConfig.validate(); err != nil {
   394  		return nil, nil, fmt.Errorf("failed driver config validation: %v", err)
   395  	}
   396  
   397  	if driverConfig.Class == "" && driverConfig.JarPath == "" {
   398  		return nil, nil, fmt.Errorf("jar_path or class must be specified")
   399  	}
   400  
   401  	absPath, err := GetAbsolutePath("java")
   402  	if err != nil {
   403  		return nil, nil, fmt.Errorf("failed to find java binary: %s", err)
   404  	}
   405  
   406  	args := javaCmdArgs(driverConfig)
   407  
   408  	d.logger.Info("starting java task", "driver_cfg", hclog.Fmt("%+v", driverConfig), "args", args)
   409  
   410  	handle := drivers.NewTaskHandle(taskHandleVersion)
   411  	handle.Config = cfg
   412  
   413  	pluginLogFile := filepath.Join(cfg.TaskDir().Dir, "executor.out")
   414  	executorConfig := &executor.ExecutorConfig{
   415  		LogFile:     pluginLogFile,
   416  		LogLevel:    "debug",
   417  		FSIsolation: capabilities.FSIsolation == drivers.FSIsolationChroot,
   418  	}
   419  
   420  	exec, pluginClient, err := executor.CreateExecutor(
   421  		d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID),
   422  		d.nomadConfig, executorConfig)
   423  	if err != nil {
   424  		return nil, nil, fmt.Errorf("failed to create executor: %v", err)
   425  	}
   426  
   427  	user := cfg.User
   428  	if user == "" {
   429  		user = "nobody"
   430  	}
   431  
   432  	if cfg.DNS != nil {
   433  		dnsMount, err := resolvconf.GenerateDNSMount(cfg.TaskDir().Dir, cfg.DNS)
   434  		if err != nil {
   435  			return nil, nil, fmt.Errorf("failed to build mount for resolv.conf: %v", err)
   436  		}
   437  		cfg.Mounts = append(cfg.Mounts, dnsMount)
   438  	}
   439  
   440  	execCmd := &executor.ExecCommand{
   441  		Cmd:              absPath,
   442  		Args:             args,
   443  		Env:              cfg.EnvList(),
   444  		User:             user,
   445  		ResourceLimits:   true,
   446  		Resources:        cfg.Resources,
   447  		TaskDir:          cfg.TaskDir().Dir,
   448  		StdoutPath:       cfg.StdoutPath,
   449  		StderrPath:       cfg.StderrPath,
   450  		Mounts:           cfg.Mounts,
   451  		Devices:          cfg.Devices,
   452  		NetworkIsolation: cfg.NetworkIsolation,
   453  		ModePID:          executor.IsolationMode(d.config.DefaultModePID, driverConfig.ModePID),
   454  		ModeIPC:          executor.IsolationMode(d.config.DefaultModeIPC, driverConfig.ModeIPC),
   455  	}
   456  
   457  	ps, err := exec.Launch(execCmd)
   458  	if err != nil {
   459  		pluginClient.Kill()
   460  		return nil, nil, fmt.Errorf("failed to launch command with executor: %v", err)
   461  	}
   462  
   463  	h := &taskHandle{
   464  		exec:         exec,
   465  		pid:          ps.Pid,
   466  		pluginClient: pluginClient,
   467  		taskConfig:   cfg,
   468  		procState:    drivers.TaskStateRunning,
   469  		startedAt:    time.Now().Round(time.Millisecond),
   470  		logger:       d.logger,
   471  	}
   472  
   473  	driverState := TaskState{
   474  		ReattachConfig: pstructs.ReattachConfigFromGoPlugin(pluginClient.ReattachConfig()),
   475  		Pid:            ps.Pid,
   476  		TaskConfig:     cfg,
   477  		StartedAt:      h.startedAt,
   478  	}
   479  
   480  	if err := handle.SetDriverState(&driverState); err != nil {
   481  		d.logger.Error("failed to start task, error setting driver state", "error", err)
   482  		exec.Shutdown("", 0)
   483  		pluginClient.Kill()
   484  		return nil, nil, fmt.Errorf("failed to set driver state: %v", err)
   485  	}
   486  
   487  	d.tasks.Set(cfg.ID, h)
   488  	go h.run()
   489  	return handle, nil, nil
   490  }
   491  
   492  func javaCmdArgs(driverConfig TaskConfig) []string {
   493  	args := []string{}
   494  	// Look for jvm options
   495  	if len(driverConfig.JvmOpts) != 0 {
   496  		args = append(args, driverConfig.JvmOpts...)
   497  	}
   498  
   499  	// Add the classpath
   500  	if driverConfig.ClassPath != "" {
   501  		args = append(args, "-cp", driverConfig.ClassPath)
   502  	}
   503  
   504  	// Add the jar
   505  	if driverConfig.JarPath != "" {
   506  		args = append(args, "-jar", driverConfig.JarPath)
   507  	}
   508  
   509  	// Add the class
   510  	if driverConfig.Class != "" {
   511  		args = append(args, driverConfig.Class)
   512  	}
   513  
   514  	// Add any args
   515  	if len(driverConfig.Args) != 0 {
   516  		args = append(args, driverConfig.Args...)
   517  	}
   518  
   519  	return args
   520  }
   521  
   522  func (d *Driver) WaitTask(ctx context.Context, taskID string) (<-chan *drivers.ExitResult, error) {
   523  	handle, ok := d.tasks.Get(taskID)
   524  	if !ok {
   525  		return nil, drivers.ErrTaskNotFound
   526  	}
   527  
   528  	ch := make(chan *drivers.ExitResult)
   529  	go d.handleWait(ctx, handle, ch)
   530  
   531  	return ch, nil
   532  }
   533  
   534  func (d *Driver) handleWait(ctx context.Context, handle *taskHandle, ch chan *drivers.ExitResult) {
   535  	defer close(ch)
   536  	var result *drivers.ExitResult
   537  	ps, err := handle.exec.Wait(ctx)
   538  	if err != nil {
   539  		result = &drivers.ExitResult{
   540  			Err: fmt.Errorf("executor: error waiting on process: %v", err),
   541  		}
   542  	} else {
   543  		result = &drivers.ExitResult{
   544  			ExitCode: ps.ExitCode,
   545  			Signal:   ps.Signal,
   546  		}
   547  	}
   548  
   549  	select {
   550  	case <-ctx.Done():
   551  		return
   552  	case <-d.ctx.Done():
   553  		return
   554  	case ch <- result:
   555  	}
   556  }
   557  
   558  func (d *Driver) StopTask(taskID string, timeout time.Duration, signal string) error {
   559  	handle, ok := d.tasks.Get(taskID)
   560  	if !ok {
   561  		return drivers.ErrTaskNotFound
   562  	}
   563  
   564  	if err := handle.exec.Shutdown(signal, timeout); err != nil {
   565  		if handle.pluginClient.Exited() {
   566  			return nil
   567  		}
   568  		return fmt.Errorf("executor Shutdown failed: %v", err)
   569  	}
   570  
   571  	return nil
   572  }
   573  
   574  func (d *Driver) DestroyTask(taskID string, force bool) error {
   575  	handle, ok := d.tasks.Get(taskID)
   576  	if !ok {
   577  		return drivers.ErrTaskNotFound
   578  	}
   579  
   580  	if handle.IsRunning() && !force {
   581  		return fmt.Errorf("cannot destroy running task")
   582  	}
   583  
   584  	if !handle.pluginClient.Exited() {
   585  		if err := handle.exec.Shutdown("", 0); err != nil {
   586  			handle.logger.Error("destroying executor failed", "err", err)
   587  		}
   588  
   589  		handle.pluginClient.Kill()
   590  	}
   591  
   592  	d.tasks.Delete(taskID)
   593  	return nil
   594  }
   595  
   596  func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) {
   597  	handle, ok := d.tasks.Get(taskID)
   598  	if !ok {
   599  		return nil, drivers.ErrTaskNotFound
   600  	}
   601  
   602  	return handle.TaskStatus(), nil
   603  }
   604  
   605  func (d *Driver) TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *drivers.TaskResourceUsage, error) {
   606  	handle, ok := d.tasks.Get(taskID)
   607  	if !ok {
   608  		return nil, drivers.ErrTaskNotFound
   609  	}
   610  
   611  	return handle.exec.Stats(ctx, interval)
   612  }
   613  
   614  func (d *Driver) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) {
   615  	return d.eventer.TaskEvents(ctx)
   616  }
   617  
   618  func (d *Driver) SignalTask(taskID string, signal string) error {
   619  	handle, ok := d.tasks.Get(taskID)
   620  	if !ok {
   621  		return drivers.ErrTaskNotFound
   622  	}
   623  
   624  	sig := os.Interrupt
   625  	if s, ok := signals.SignalLookup[signal]; ok {
   626  		sig = s
   627  	} else {
   628  		d.logger.Warn("unknown signal to send to task, using SIGINT instead", "signal", signal, "task_id", handle.taskConfig.ID)
   629  
   630  	}
   631  	return handle.exec.Signal(sig)
   632  }
   633  
   634  func (d *Driver) ExecTask(taskID string, cmd []string, timeout time.Duration) (*drivers.ExecTaskResult, error) {
   635  	if len(cmd) == 0 {
   636  		return nil, fmt.Errorf("error cmd must have at least one value")
   637  	}
   638  	handle, ok := d.tasks.Get(taskID)
   639  	if !ok {
   640  		return nil, drivers.ErrTaskNotFound
   641  	}
   642  
   643  	out, exitCode, err := handle.exec.Exec(time.Now().Add(timeout), cmd[0], cmd[1:])
   644  	if err != nil {
   645  		return nil, err
   646  	}
   647  
   648  	return &drivers.ExecTaskResult{
   649  		Stdout: out,
   650  		ExitResult: &drivers.ExitResult{
   651  			ExitCode: exitCode,
   652  		},
   653  	}, nil
   654  }
   655  
   656  var _ drivers.ExecTaskStreamingRawDriver = (*Driver)(nil)
   657  
   658  func (d *Driver) ExecTaskStreamingRaw(ctx context.Context,
   659  	taskID string,
   660  	command []string,
   661  	tty bool,
   662  	stream drivers.ExecTaskStream) error {
   663  
   664  	if len(command) == 0 {
   665  		return fmt.Errorf("error cmd must have at least one value")
   666  	}
   667  	handle, ok := d.tasks.Get(taskID)
   668  	if !ok {
   669  		return drivers.ErrTaskNotFound
   670  	}
   671  
   672  	return handle.exec.ExecStreaming(ctx, command, tty, stream)
   673  }
   674  
   675  // GetAbsolutePath returns the absolute path of the passed binary by resolving
   676  // it in the path and following symlinks.
   677  func GetAbsolutePath(bin string) (string, error) {
   678  	lp, err := exec.LookPath(bin)
   679  	if err != nil {
   680  		return "", fmt.Errorf("failed to resolve path to %q executable: %v", bin, err)
   681  	}
   682  
   683  	return filepath.EvalSymlinks(lp)
   684  }