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

     1  package qemu
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/coreos/go-semver/semver"
    16  	hclog "github.com/hashicorp/go-hclog"
    17  	"github.com/hashicorp/nomad/client/taskenv"
    18  	"github.com/hashicorp/nomad/drivers/shared/eventer"
    19  	"github.com/hashicorp/nomad/drivers/shared/executor"
    20  	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
    21  	"github.com/hashicorp/nomad/helper/pluginutils/loader"
    22  	"github.com/hashicorp/nomad/plugins/base"
    23  	"github.com/hashicorp/nomad/plugins/drivers"
    24  	"github.com/hashicorp/nomad/plugins/shared/hclspec"
    25  	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
    26  )
    27  
    28  const (
    29  	// pluginName is the name of the plugin
    30  	pluginName = "qemu"
    31  
    32  	// fingerprintPeriod is the interval at which the driver will send fingerprint responses
    33  	fingerprintPeriod = 30 * time.Second
    34  
    35  	// The key populated in Node Attributes to indicate presence of the Qemu driver
    36  	driverAttr        = "driver.qemu"
    37  	driverVersionAttr = "driver.qemu.version"
    38  
    39  	// Represents an ACPI shutdown request to the VM (emulates pressing a physical power button)
    40  	// Reference: https://en.wikibooks.org/wiki/QEMU/Monitor
    41  	qemuGracefulShutdownMsg = "system_powerdown\n"
    42  	qemuMonitorSocketName   = "qemu-monitor.sock"
    43  
    44  	// Maximum socket path length prior to qemu 2.10.1
    45  	qemuLegacyMaxMonitorPathLen = 108
    46  
    47  	// taskHandleVersion is the version of task handle which this driver sets
    48  	// and understands how to decode driver state
    49  	taskHandleVersion = 1
    50  )
    51  
    52  var (
    53  	// PluginID is the qemu plugin metadata registered in the plugin
    54  	// catalog.
    55  	PluginID = loader.PluginID{
    56  		Name:       pluginName,
    57  		PluginType: base.PluginTypeDriver,
    58  	}
    59  
    60  	// PluginConfig is the qemu driver factory function registered in the
    61  	// plugin catalog.
    62  	PluginConfig = &loader.InternalPluginConfig{
    63  		Config:  map[string]interface{}{},
    64  		Factory: func(ctx context.Context, l hclog.Logger) interface{} { return NewQemuDriver(ctx, l) },
    65  	}
    66  
    67  	versionRegex = regexp.MustCompile(`version (\d[\.\d+]+)`)
    68  
    69  	// Prior to qemu 2.10.1, monitor socket paths are truncated to 108 bytes.
    70  	// We should consider this if driver.qemu.version is < 2.10.1 and the
    71  	// generated monitor path is too long.
    72  	//
    73  	// Relevant fix is here:
    74  	// https://github.com/qemu/qemu/commit/ad9579aaa16d5b385922d49edac2c96c79bcfb6
    75  	qemuVersionLongSocketPathFix = semver.New("2.10.1")
    76  
    77  	// pluginInfo is the response returned for the PluginInfo RPC
    78  	pluginInfo = &base.PluginInfoResponse{
    79  		Type:              base.PluginTypeDriver,
    80  		PluginApiVersions: []string{drivers.ApiVersion010},
    81  		PluginVersion:     "0.1.0",
    82  		Name:              pluginName,
    83  	}
    84  
    85  	// configSpec is the hcl specification returned by the ConfigSchema RPC
    86  	configSpec = hclspec.NewObject(map[string]*hclspec.Spec{
    87  		"image_paths": hclspec.NewAttr("image_paths", "list(string)", false),
    88  	})
    89  
    90  	// taskConfigSpec is the hcl specification for the driver config section of
    91  	// a taskConfig within a job. It is returned in the TaskConfigSchema RPC
    92  	taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{
    93  		"image_path":        hclspec.NewAttr("image_path", "string", true),
    94  		"accelerator":       hclspec.NewAttr("accelerator", "string", false),
    95  		"graceful_shutdown": hclspec.NewAttr("graceful_shutdown", "bool", false),
    96  		"args":              hclspec.NewAttr("args", "list(string)", false),
    97  		"port_map":          hclspec.NewAttr("port_map", "list(map(number))", false),
    98  	})
    99  
   100  	// capabilities is returned by the Capabilities RPC and indicates what
   101  	// optional features this driver supports
   102  	capabilities = &drivers.Capabilities{
   103  		SendSignals: false,
   104  		Exec:        false,
   105  		FSIsolation: drivers.FSIsolationImage,
   106  		NetIsolationModes: []drivers.NetIsolationMode{
   107  			drivers.NetIsolationModeHost,
   108  			drivers.NetIsolationModeGroup,
   109  		},
   110  		MountConfigs: drivers.MountConfigSupportNone,
   111  	}
   112  
   113  	_ drivers.DriverPlugin = (*Driver)(nil)
   114  )
   115  
   116  // TaskConfig is the driver configuration of a taskConfig within a job
   117  type TaskConfig struct {
   118  	ImagePath        string             `codec:"image_path"`
   119  	Accelerator      string             `codec:"accelerator"`
   120  	Args             []string           `codec:"args"`     // extra arguments to qemu executable
   121  	PortMap          hclutils.MapStrInt `codec:"port_map"` // A map of host port and the port name defined in the image manifest file
   122  	GracefulShutdown bool               `codec:"graceful_shutdown"`
   123  }
   124  
   125  // TaskState is the state which is encoded in the handle returned in StartTask.
   126  // This information is needed to rebuild the taskConfig state and handler
   127  // during recovery.
   128  type TaskState struct {
   129  	ReattachConfig *pstructs.ReattachConfig
   130  	TaskConfig     *drivers.TaskConfig
   131  	Pid            int
   132  	StartedAt      time.Time
   133  }
   134  
   135  // Config is the driver configuration set by SetConfig RPC call
   136  type Config struct {
   137  	// ImagePaths is an allow-list of paths qemu is allowed to load an image from
   138  	ImagePaths []string `codec:"image_paths"`
   139  }
   140  
   141  // Driver is a driver for running images via Qemu
   142  type Driver struct {
   143  	// eventer is used to handle multiplexing of TaskEvents calls such that an
   144  	// event can be broadcast to all callers
   145  	eventer *eventer.Eventer
   146  
   147  	// config is the driver configuration set by the SetConfig RPC
   148  	config Config
   149  
   150  	// tasks is the in memory datastore mapping taskIDs to qemuTaskHandle
   151  	tasks *taskStore
   152  
   153  	// ctx is the context for the driver. It is passed to other subsystems to
   154  	// coordinate shutdown
   155  	ctx context.Context
   156  
   157  	// nomadConf is the client agent's configuration
   158  	nomadConfig *base.ClientDriverConfig
   159  
   160  	// logger will log to the Nomad agent
   161  	logger hclog.Logger
   162  }
   163  
   164  func NewQemuDriver(ctx context.Context, logger hclog.Logger) drivers.DriverPlugin {
   165  	logger = logger.Named(pluginName)
   166  	return &Driver{
   167  		eventer: eventer.NewEventer(ctx, logger),
   168  		tasks:   newTaskStore(),
   169  		ctx:     ctx,
   170  		logger:  logger,
   171  	}
   172  }
   173  
   174  func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) {
   175  	return pluginInfo, nil
   176  }
   177  
   178  func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
   179  	return configSpec, nil
   180  }
   181  
   182  func (d *Driver) SetConfig(cfg *base.Config) error {
   183  	var config Config
   184  	if len(cfg.PluginConfig) != 0 {
   185  		if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil {
   186  			return err
   187  		}
   188  	}
   189  
   190  	d.config = config
   191  	if cfg.AgentConfig != nil {
   192  		d.nomadConfig = cfg.AgentConfig.Driver
   193  	}
   194  	return nil
   195  }
   196  
   197  func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) {
   198  	return taskConfigSpec, nil
   199  }
   200  
   201  func (d *Driver) Capabilities() (*drivers.Capabilities, error) {
   202  	return capabilities, nil
   203  }
   204  
   205  func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) {
   206  	ch := make(chan *drivers.Fingerprint)
   207  	go d.handleFingerprint(ctx, ch)
   208  	return ch, nil
   209  }
   210  
   211  func (d *Driver) handleFingerprint(ctx context.Context, ch chan *drivers.Fingerprint) {
   212  	ticker := time.NewTimer(0)
   213  	for {
   214  		select {
   215  		case <-ctx.Done():
   216  			return
   217  		case <-d.ctx.Done():
   218  			return
   219  		case <-ticker.C:
   220  			ticker.Reset(fingerprintPeriod)
   221  			ch <- d.buildFingerprint()
   222  		}
   223  	}
   224  }
   225  
   226  func (d *Driver) buildFingerprint() *drivers.Fingerprint {
   227  	fingerprint := &drivers.Fingerprint{
   228  		Attributes:        map[string]*pstructs.Attribute{},
   229  		Health:            drivers.HealthStateHealthy,
   230  		HealthDescription: drivers.DriverHealthy,
   231  	}
   232  
   233  	bin := "qemu-system-x86_64"
   234  	if runtime.GOOS == "windows" {
   235  		// On windows, the "qemu-system-x86_64" command does not respond to the
   236  		// version flag.
   237  		bin = "qemu-img"
   238  	}
   239  	outBytes, err := exec.Command(bin, "--version").Output()
   240  	if err != nil {
   241  		// return no error, as it isn't an error to not find qemu, it just means we
   242  		// can't use it.
   243  		fingerprint.Health = drivers.HealthStateUndetected
   244  		fingerprint.HealthDescription = ""
   245  		return fingerprint
   246  	}
   247  	out := strings.TrimSpace(string(outBytes))
   248  
   249  	matches := versionRegex.FindStringSubmatch(out)
   250  	if len(matches) != 2 {
   251  		fingerprint.Health = drivers.HealthStateUndetected
   252  		fingerprint.HealthDescription = fmt.Sprintf("Failed to parse qemu version from %v", out)
   253  		return fingerprint
   254  	}
   255  	currentQemuVersion := matches[1]
   256  	fingerprint.Attributes[driverAttr] = pstructs.NewBoolAttribute(true)
   257  	fingerprint.Attributes[driverVersionAttr] = pstructs.NewStringAttribute(currentQemuVersion)
   258  	return fingerprint
   259  }
   260  
   261  func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error {
   262  	if handle == nil {
   263  		return fmt.Errorf("error: handle cannot be nil")
   264  	}
   265  
   266  	// COMPAT(0.10): pre 0.9 upgrade path check
   267  	if handle.Version == 0 {
   268  		return d.recoverPre09Task(handle)
   269  	}
   270  
   271  	// If already attached to handle there's nothing to recover.
   272  	if _, ok := d.tasks.Get(handle.Config.ID); ok {
   273  		d.logger.Trace("nothing to recover; task already exists",
   274  			"task_id", handle.Config.ID,
   275  			"task_name", handle.Config.Name,
   276  		)
   277  		return nil
   278  	}
   279  
   280  	var taskState TaskState
   281  	if err := handle.GetDriverState(&taskState); err != nil {
   282  		d.logger.Error("failed to decode taskConfig state from handle", "error", err, "task_id", handle.Config.ID)
   283  		return fmt.Errorf("failed to decode taskConfig state from handle: %v", err)
   284  	}
   285  
   286  	plugRC, err := pstructs.ReattachConfigToGoPlugin(taskState.ReattachConfig)
   287  	if err != nil {
   288  		d.logger.Error("failed to build ReattachConfig from taskConfig state", "error", err, "task_id", handle.Config.ID)
   289  		return fmt.Errorf("failed to build ReattachConfig from taskConfig state: %v", err)
   290  	}
   291  
   292  	execImpl, pluginClient, err := executor.ReattachToExecutor(plugRC,
   293  		d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID))
   294  	if err != nil {
   295  		d.logger.Error("failed to reattach to executor", "error", err, "task_id", handle.Config.ID)
   296  		return fmt.Errorf("failed to reattach to executor: %v", err)
   297  	}
   298  
   299  	h := &taskHandle{
   300  		exec:         execImpl,
   301  		pid:          taskState.Pid,
   302  		pluginClient: pluginClient,
   303  		taskConfig:   taskState.TaskConfig,
   304  		procState:    drivers.TaskStateRunning,
   305  		startedAt:    taskState.StartedAt,
   306  		exitResult:   &drivers.ExitResult{},
   307  		logger:       d.logger,
   308  	}
   309  
   310  	d.tasks.Set(taskState.TaskConfig.ID, h)
   311  
   312  	go h.run()
   313  	return nil
   314  }
   315  
   316  func isAllowedImagePath(allowedPaths []string, allocDir, imagePath string) bool {
   317  	if !filepath.IsAbs(imagePath) {
   318  		imagePath = filepath.Join(allocDir, imagePath)
   319  	}
   320  
   321  	isParent := func(parent, path string) bool {
   322  		rel, err := filepath.Rel(parent, path)
   323  		return err == nil && !strings.HasPrefix(rel, "..")
   324  	}
   325  
   326  	// check if path is under alloc dir
   327  	if isParent(allocDir, imagePath) {
   328  		return true
   329  	}
   330  
   331  	// check allowed paths
   332  	for _, ap := range allowedPaths {
   333  		if isParent(ap, imagePath) {
   334  			return true
   335  		}
   336  	}
   337  
   338  	return false
   339  }
   340  
   341  func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
   342  	if _, ok := d.tasks.Get(cfg.ID); ok {
   343  		return nil, nil, fmt.Errorf("taskConfig with ID '%s' already started", cfg.ID)
   344  	}
   345  
   346  	var driverConfig TaskConfig
   347  
   348  	if err := cfg.DecodeDriverConfig(&driverConfig); err != nil {
   349  		return nil, nil, fmt.Errorf("failed to decode driver config: %v", err)
   350  	}
   351  
   352  	// ensure that PortMap variables are populated early on
   353  	cfg.Env = taskenv.SetPortMapEnvs(cfg.Env, driverConfig.PortMap)
   354  
   355  	handle := drivers.NewTaskHandle(taskHandleVersion)
   356  	handle.Config = cfg
   357  
   358  	// Get the image source
   359  	vmPath := driverConfig.ImagePath
   360  	if vmPath == "" {
   361  		return nil, nil, fmt.Errorf("image_path must be set")
   362  	}
   363  	vmID := filepath.Base(vmPath)
   364  
   365  	if !isAllowedImagePath(d.config.ImagePaths, cfg.AllocDir, vmPath) {
   366  		return nil, nil, fmt.Errorf("image_path is not in the allowed paths")
   367  	}
   368  
   369  	// Parse configuration arguments
   370  	// Create the base arguments
   371  	accelerator := "tcg"
   372  	if driverConfig.Accelerator != "" {
   373  		accelerator = driverConfig.Accelerator
   374  	}
   375  
   376  	mb := cfg.Resources.NomadResources.Memory.MemoryMB
   377  	if mb < 128 || mb > 4000000 {
   378  		return nil, nil, fmt.Errorf("Qemu memory assignment out of bounds")
   379  	}
   380  	mem := fmt.Sprintf("%dM", mb)
   381  
   382  	absPath, err := GetAbsolutePath("qemu-system-x86_64")
   383  	if err != nil {
   384  		return nil, nil, err
   385  	}
   386  
   387  	args := []string{
   388  		absPath,
   389  		"-machine", "type=pc,accel=" + accelerator,
   390  		"-name", vmID,
   391  		"-m", mem,
   392  		"-drive", "file=" + vmPath,
   393  		"-nographic",
   394  	}
   395  
   396  	var netdevArgs []string
   397  	if cfg.DNS != nil {
   398  		if len(cfg.DNS.Servers) > 0 {
   399  			netdevArgs = append(netdevArgs, "dns="+cfg.DNS.Servers[0])
   400  		}
   401  
   402  		for _, s := range cfg.DNS.Searches {
   403  			netdevArgs = append(netdevArgs, "dnssearch="+s)
   404  		}
   405  	}
   406  
   407  	var monitorPath string
   408  	if driverConfig.GracefulShutdown {
   409  		if runtime.GOOS == "windows" {
   410  			return nil, nil, errors.New("QEMU graceful shutdown is unsupported on the Windows platform")
   411  		}
   412  		// This socket will be used to manage the virtual machine (for example,
   413  		// to perform graceful shutdowns)
   414  		taskDir := filepath.Join(cfg.AllocDir, cfg.Name)
   415  		fingerPrint := d.buildFingerprint()
   416  		if fingerPrint.Attributes == nil {
   417  			return nil, nil, fmt.Errorf("unable to get qemu driver version from fingerprinted attributes")
   418  		}
   419  		monitorPath, err = d.getMonitorPath(taskDir, fingerPrint)
   420  		if err != nil {
   421  			d.logger.Debug("could not get qemu monitor path", "error", err)
   422  			return nil, nil, err
   423  		}
   424  		d.logger.Debug("got monitor path", "monitorPath", monitorPath)
   425  		args = append(args, "-monitor", fmt.Sprintf("unix:%s,server,nowait", monitorPath))
   426  	}
   427  
   428  	// Add pass through arguments to qemu executable. A user can specify
   429  	// these arguments in driver task configuration. These arguments are
   430  	// passed directly to the qemu driver as command line options.
   431  	// For example, args = [ "-nodefconfig", "-nodefaults" ]
   432  	// This will allow a VM with embedded configuration to boot successfully.
   433  	args = append(args, driverConfig.Args...)
   434  
   435  	// Check the Resources required Networks to add port mappings. If no resources
   436  	// are required, we assume the VM is a purely compute job and does not require
   437  	// the outside world to be able to reach it. VMs ran without port mappings can
   438  	// still reach out to the world, but without port mappings it is effectively
   439  	// firewalled
   440  	protocols := []string{"udp", "tcp"}
   441  	if len(cfg.Resources.NomadResources.Networks) > 0 {
   442  		// Loop through the port map and construct the hostfwd string, to map
   443  		// reserved ports to the ports listenting in the VM
   444  		// Ex: hostfwd=tcp::22000-:22,hostfwd=tcp::80-:8080
   445  		taskPorts := cfg.Resources.NomadResources.Networks[0].PortLabels()
   446  		for label, guest := range driverConfig.PortMap {
   447  			host, ok := taskPorts[label]
   448  			if !ok {
   449  				return nil, nil, fmt.Errorf("Unknown port label %q", label)
   450  			}
   451  
   452  			for _, p := range protocols {
   453  				netdevArgs = append(netdevArgs, fmt.Sprintf("hostfwd=%s::%d-:%d", p, host, guest))
   454  			}
   455  		}
   456  
   457  		if len(netdevArgs) != 0 {
   458  			args = append(args,
   459  				"-netdev",
   460  				fmt.Sprintf("user,id=user.0,%s", strings.Join(netdevArgs, ",")),
   461  				"-device", "virtio-net,netdev=user.0",
   462  			)
   463  		}
   464  	}
   465  
   466  	// If using KVM, add optimization args
   467  	if accelerator == "kvm" {
   468  		if runtime.GOOS == "windows" {
   469  			return nil, nil, errors.New("KVM accelerator is unsupported on the Windows platform")
   470  		}
   471  		args = append(args,
   472  			"-enable-kvm",
   473  			"-cpu", "host",
   474  			// Do we have cores information available to the Driver?
   475  			// "-smp", fmt.Sprintf("%d", cores),
   476  		)
   477  	}
   478  	d.logger.Debug("starting QemuVM command ", "args", strings.Join(args, " "))
   479  
   480  	pluginLogFile := filepath.Join(cfg.TaskDir().Dir, fmt.Sprintf("%s-executor.out", cfg.Name))
   481  	executorConfig := &executor.ExecutorConfig{
   482  		LogFile:  pluginLogFile,
   483  		LogLevel: "debug",
   484  	}
   485  
   486  	execImpl, pluginClient, err := executor.CreateExecutor(
   487  		d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID),
   488  		d.nomadConfig, executorConfig)
   489  	if err != nil {
   490  		return nil, nil, err
   491  	}
   492  
   493  	execCmd := &executor.ExecCommand{
   494  		Cmd:              args[0],
   495  		Args:             args[1:],
   496  		Env:              cfg.EnvList(),
   497  		User:             cfg.User,
   498  		TaskDir:          cfg.TaskDir().Dir,
   499  		StdoutPath:       cfg.StdoutPath,
   500  		StderrPath:       cfg.StderrPath,
   501  		NetworkIsolation: cfg.NetworkIsolation,
   502  	}
   503  	ps, err := execImpl.Launch(execCmd)
   504  	if err != nil {
   505  		pluginClient.Kill()
   506  		return nil, nil, err
   507  	}
   508  	d.logger.Debug("started new QemuVM", "ID", vmID)
   509  
   510  	h := &taskHandle{
   511  		exec:         execImpl,
   512  		pid:          ps.Pid,
   513  		monitorPath:  monitorPath,
   514  		pluginClient: pluginClient,
   515  		taskConfig:   cfg,
   516  		procState:    drivers.TaskStateRunning,
   517  		startedAt:    time.Now().Round(time.Millisecond),
   518  		logger:       d.logger,
   519  	}
   520  
   521  	qemuDriverState := TaskState{
   522  		ReattachConfig: pstructs.ReattachConfigFromGoPlugin(pluginClient.ReattachConfig()),
   523  		Pid:            ps.Pid,
   524  		TaskConfig:     cfg,
   525  		StartedAt:      h.startedAt,
   526  	}
   527  
   528  	if err := handle.SetDriverState(&qemuDriverState); err != nil {
   529  		d.logger.Error("failed to start task, error setting driver state", "error", err)
   530  		execImpl.Shutdown("", 0)
   531  		pluginClient.Kill()
   532  		return nil, nil, fmt.Errorf("failed to set driver state: %v", err)
   533  	}
   534  
   535  	d.tasks.Set(cfg.ID, h)
   536  	go h.run()
   537  
   538  	var driverNetwork *drivers.DriverNetwork
   539  	if len(driverConfig.PortMap) == 1 {
   540  		driverNetwork = &drivers.DriverNetwork{
   541  			PortMap: driverConfig.PortMap,
   542  		}
   543  	}
   544  	return handle, driverNetwork, nil
   545  }
   546  
   547  func (d *Driver) WaitTask(ctx context.Context, taskID string) (<-chan *drivers.ExitResult, error) {
   548  	handle, ok := d.tasks.Get(taskID)
   549  	if !ok {
   550  		return nil, drivers.ErrTaskNotFound
   551  	}
   552  
   553  	ch := make(chan *drivers.ExitResult)
   554  	go d.handleWait(ctx, handle, ch)
   555  
   556  	return ch, nil
   557  }
   558  
   559  func (d *Driver) StopTask(taskID string, timeout time.Duration, signal string) error {
   560  	handle, ok := d.tasks.Get(taskID)
   561  	if !ok {
   562  		return drivers.ErrTaskNotFound
   563  	}
   564  
   565  	// Attempt a graceful shutdown only if it was configured in the job
   566  	if handle.monitorPath != "" {
   567  		if err := sendQemuShutdown(d.logger, handle.monitorPath, handle.pid); err != nil {
   568  			d.logger.Debug("error sending graceful shutdown ", "pid", handle.pid, "error", err)
   569  		}
   570  	}
   571  
   572  	// TODO(preetha) we are calling shutdown on the executor here
   573  	// after attempting a graceful qemu shutdown, qemu process may
   574  	// not be around when we call exec.shutdown
   575  	if err := handle.exec.Shutdown(signal, timeout); err != nil {
   576  		if handle.pluginClient.Exited() {
   577  			return nil
   578  		}
   579  		return fmt.Errorf("executor Shutdown failed: %v", err)
   580  	}
   581  
   582  	return nil
   583  }
   584  
   585  func (d *Driver) DestroyTask(taskID string, force bool) error {
   586  	handle, ok := d.tasks.Get(taskID)
   587  	if !ok {
   588  		return drivers.ErrTaskNotFound
   589  	}
   590  
   591  	if handle.IsRunning() && !force {
   592  		return fmt.Errorf("cannot destroy running task")
   593  	}
   594  
   595  	if !handle.pluginClient.Exited() {
   596  		if err := handle.exec.Shutdown("", 0); err != nil {
   597  			handle.logger.Error("destroying executor failed", "err", err)
   598  		}
   599  
   600  		handle.pluginClient.Kill()
   601  	}
   602  
   603  	d.tasks.Delete(taskID)
   604  	return nil
   605  }
   606  
   607  func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) {
   608  	handle, ok := d.tasks.Get(taskID)
   609  	if !ok {
   610  		return nil, drivers.ErrTaskNotFound
   611  	}
   612  
   613  	return handle.TaskStatus(), nil
   614  }
   615  
   616  func (d *Driver) TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *drivers.TaskResourceUsage, error) {
   617  	handle, ok := d.tasks.Get(taskID)
   618  	if !ok {
   619  		return nil, drivers.ErrTaskNotFound
   620  	}
   621  
   622  	return handle.exec.Stats(ctx, interval)
   623  }
   624  
   625  func (d *Driver) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) {
   626  	return d.eventer.TaskEvents(ctx)
   627  }
   628  
   629  func (d *Driver) SignalTask(taskID string, signal string) error {
   630  	return fmt.Errorf("Qemu driver can't signal commands")
   631  }
   632  
   633  func (d *Driver) ExecTask(taskID string, cmdArgs []string, timeout time.Duration) (*drivers.ExecTaskResult, error) {
   634  	return nil, fmt.Errorf("Qemu driver can't execute commands")
   635  
   636  }
   637  
   638  // GetAbsolutePath returns the absolute path of the passed binary by resolving
   639  // it in the path and following symlinks.
   640  func GetAbsolutePath(bin string) (string, error) {
   641  	lp, err := exec.LookPath(bin)
   642  	if err != nil {
   643  		return "", fmt.Errorf("failed to resolve path to %q executable: %v", bin, err)
   644  	}
   645  
   646  	return filepath.EvalSymlinks(lp)
   647  }
   648  
   649  func (d *Driver) handleWait(ctx context.Context, handle *taskHandle, ch chan *drivers.ExitResult) {
   650  	defer close(ch)
   651  	var result *drivers.ExitResult
   652  	ps, err := handle.exec.Wait(ctx)
   653  	if err != nil {
   654  		result = &drivers.ExitResult{
   655  			Err: fmt.Errorf("executor: error waiting on process: %v", err),
   656  		}
   657  	} else {
   658  		result = &drivers.ExitResult{
   659  			ExitCode: ps.ExitCode,
   660  			Signal:   ps.Signal,
   661  		}
   662  	}
   663  
   664  	select {
   665  	case <-ctx.Done():
   666  	case <-d.ctx.Done():
   667  	case ch <- result:
   668  	}
   669  }
   670  
   671  // getMonitorPath is used to determine whether a qemu monitor socket can be
   672  // safely created and accessed in the task directory by the version of qemu
   673  // present on the host. If it is safe to use, the socket's full path is
   674  // returned along with a nil error. Otherwise, an empty string is returned
   675  // along with a descriptive error.
   676  func (d *Driver) getMonitorPath(dir string, fingerPrint *drivers.Fingerprint) (string, error) {
   677  	var longPathSupport bool
   678  	currentQemuVer := fingerPrint.Attributes[driverVersionAttr]
   679  	currentQemuSemver := semver.New(currentQemuVer.GoString())
   680  	if currentQemuSemver.LessThan(*qemuVersionLongSocketPathFix) {
   681  		longPathSupport = false
   682  		d.logger.Debug("long socket paths are not available in this version of QEMU", "version", currentQemuVer)
   683  	} else {
   684  		longPathSupport = true
   685  		d.logger.Debug("long socket paths available in this version of QEMU", "version", currentQemuVer)
   686  	}
   687  	fullSocketPath := fmt.Sprintf("%s/%s", dir, qemuMonitorSocketName)
   688  	if len(fullSocketPath) > qemuLegacyMaxMonitorPathLen && !longPathSupport {
   689  		return "", fmt.Errorf("monitor path is too long for this version of qemu")
   690  	}
   691  	return fullSocketPath, nil
   692  }
   693  
   694  // sendQemuShutdown attempts to issue an ACPI power-off command via the qemu
   695  // monitor
   696  func sendQemuShutdown(logger hclog.Logger, monitorPath string, userPid int) error {
   697  	if monitorPath == "" {
   698  		return errors.New("monitorPath not set")
   699  	}
   700  	monitorSocket, err := net.Dial("unix", monitorPath)
   701  	if err != nil {
   702  		logger.Warn("could not connect to qemu monitor", "pid", userPid, "monitorPath", monitorPath, "error", err)
   703  		return err
   704  	}
   705  	defer monitorSocket.Close()
   706  	logger.Debug("sending graceful shutdown command to qemu monitor socket", "monitor_path", monitorPath, "pid", userPid)
   707  	_, err = monitorSocket.Write([]byte(qemuGracefulShutdownMsg))
   708  	if err != nil {
   709  		logger.Warn("failed to send shutdown message", "shutdown message", qemuGracefulShutdownMsg, "monitorPath", monitorPath, "userPid", userPid, "error", err)
   710  	}
   711  	return err
   712  }