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