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