github.com/anuvu/nomad@v0.8.7-atom1/client/driver/qemu.go (about)

     1  package driver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"log"
     9  	"net"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"regexp"
    14  	"runtime"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/coreos/go-semver/semver"
    19  	plugin "github.com/hashicorp/go-plugin"
    20  	"github.com/hashicorp/nomad/client/driver/executor"
    21  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    22  	"github.com/hashicorp/nomad/client/fingerprint"
    23  	cstructs "github.com/hashicorp/nomad/client/structs"
    24  	"github.com/hashicorp/nomad/helper/fields"
    25  	"github.com/hashicorp/nomad/nomad/structs"
    26  	"github.com/mitchellh/mapstructure"
    27  )
    28  
    29  var (
    30  	reQemuVersion = regexp.MustCompile(`version (\d[\.\d+]+)`)
    31  
    32  	// Prior to qemu 2.10.1, monitor socket paths are truncated to 108 bytes.
    33  	// We should consider this if driver.qemu.version is < 2.10.1 and the
    34  	// generated monitor path is too long.
    35  
    36  	//
    37  	// Relevant fix is here:
    38  	// https://github.com/qemu/qemu/commit/ad9579aaa16d5b385922d49edac2c96c79bcfb6
    39  	qemuVersionLongSocketPathFix = semver.New("2.10.1")
    40  )
    41  
    42  const (
    43  	// The key populated in Node Attributes to indicate presence of the Qemu driver
    44  	qemuDriverAttr        = "driver.qemu"
    45  	qemuDriverVersionAttr = "driver.qemu.version"
    46  	// Represents an ACPI shutdown request to the VM (emulates pressing a physical power button)
    47  	// Reference: https://en.wikibooks.org/wiki/QEMU/Monitor
    48  	qemuGracefulShutdownMsg = "system_powerdown\n"
    49  	qemuMonitorSocketName   = "qemu-monitor.sock"
    50  	// Maximum socket path length prior to qemu 2.10.1
    51  	qemuLegacyMaxMonitorPathLen = 108
    52  )
    53  
    54  // QemuDriver is a driver for running images via Qemu
    55  // We attempt to chose sane defaults for now, with more configuration available
    56  // planned in the future
    57  type QemuDriver struct {
    58  	DriverContext
    59  	fingerprint.StaticFingerprinter
    60  
    61  	driverConfig *QemuDriverConfig
    62  }
    63  
    64  type QemuDriverConfig struct {
    65  	ImagePath        string           `mapstructure:"image_path"`
    66  	Accelerator      string           `mapstructure:"accelerator"`
    67  	GracefulShutdown bool             `mapstructure:"graceful_shutdown"`
    68  	PortMap          []map[string]int `mapstructure:"port_map"` // A map of host port labels and to guest ports.
    69  	Args             []string         `mapstructure:"args"`     // extra arguments to qemu executable
    70  }
    71  
    72  // qemuHandle is returned from Start/Open as a handle to the PID
    73  type qemuHandle struct {
    74  	pluginClient   *plugin.Client
    75  	userPid        int
    76  	executor       executor.Executor
    77  	monitorPath    string
    78  	killTimeout    time.Duration
    79  	maxKillTimeout time.Duration
    80  	logger         *log.Logger
    81  	version        string
    82  	waitCh         chan *dstructs.WaitResult
    83  	doneCh         chan struct{}
    84  }
    85  
    86  // getMonitorPath is used to determine whether a qemu monitor socket can be
    87  // safely created and accessed in the task directory by the version of qemu
    88  // present on the host. If it is safe to use, the socket's full path is
    89  // returned along with a nil error. Otherwise, an empty string is returned
    90  // along with a descriptive error.
    91  func (d *QemuDriver) getMonitorPath(dir string) (string, error) {
    92  	var longPathSupport bool
    93  	currentQemuVer := d.DriverContext.node.Attributes[qemuDriverVersionAttr]
    94  	currentQemuSemver := semver.New(currentQemuVer)
    95  	if currentQemuSemver.LessThan(*qemuVersionLongSocketPathFix) {
    96  		longPathSupport = false
    97  		d.logger.Printf("[DEBUG] driver.qemu: long socket paths are not available in this version of QEMU (%s)", currentQemuVer)
    98  	} else {
    99  		longPathSupport = true
   100  		d.logger.Printf("[DEBUG] driver.qemu: long socket paths available in this version of QEMU (%s)", currentQemuVer)
   101  	}
   102  	fullSocketPath := fmt.Sprintf("%s/%s", dir, qemuMonitorSocketName)
   103  	if len(fullSocketPath) > qemuLegacyMaxMonitorPathLen && longPathSupport == false {
   104  		return "", fmt.Errorf("monitor path is too long for this version of qemu")
   105  	}
   106  	return fullSocketPath, nil
   107  }
   108  
   109  // NewQemuDriver is used to create a new exec driver
   110  func NewQemuDriver(ctx *DriverContext) Driver {
   111  	return &QemuDriver{DriverContext: *ctx}
   112  }
   113  
   114  // Validate is used to validate the driver configuration
   115  func (d *QemuDriver) Validate(config map[string]interface{}) error {
   116  	fd := &fields.FieldData{
   117  		Raw: config,
   118  		Schema: map[string]*fields.FieldSchema{
   119  			"image_path": {
   120  				Type:     fields.TypeString,
   121  				Required: true,
   122  			},
   123  			"accelerator": {
   124  				Type: fields.TypeString,
   125  			},
   126  			"graceful_shutdown": {
   127  				Type:     fields.TypeBool,
   128  				Required: false,
   129  			},
   130  			"port_map": {
   131  				Type: fields.TypeArray,
   132  			},
   133  			"args": {
   134  				Type: fields.TypeArray,
   135  			},
   136  		},
   137  	}
   138  
   139  	if err := fd.Validate(); err != nil {
   140  		return err
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  func (d *QemuDriver) Abilities() DriverAbilities {
   147  	return DriverAbilities{
   148  		SendSignals: false,
   149  		Exec:        false,
   150  	}
   151  }
   152  
   153  func (d *QemuDriver) FSIsolation() cstructs.FSIsolation {
   154  	return cstructs.FSIsolationImage
   155  }
   156  
   157  func (d *QemuDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error {
   158  	bin := "qemu-system-x86_64"
   159  	if runtime.GOOS == "windows" {
   160  		// On windows, the "qemu-system-x86_64" command does not respond to the
   161  		// version flag.
   162  		bin = "qemu-img"
   163  	}
   164  	outBytes, err := exec.Command(bin, "--version").Output()
   165  	if err != nil {
   166  		// return no error, as it isn't an error to not find qemu, it just means we
   167  		// can't use it.
   168  		return nil
   169  	}
   170  	out := strings.TrimSpace(string(outBytes))
   171  
   172  	matches := reQemuVersion.FindStringSubmatch(out)
   173  	if len(matches) != 2 {
   174  		resp.RemoveAttribute(qemuDriverAttr)
   175  		return fmt.Errorf("Unable to parse Qemu version string: %#v", matches)
   176  	}
   177  	currentQemuVersion := matches[1]
   178  
   179  	resp.AddAttribute(qemuDriverAttr, "1")
   180  	resp.AddAttribute(qemuDriverVersionAttr, currentQemuVersion)
   181  	resp.Detected = true
   182  
   183  	return nil
   184  }
   185  
   186  func (d *QemuDriver) Prestart(_ *ExecContext, task *structs.Task) (*PrestartResponse, error) {
   187  	var driverConfig QemuDriverConfig
   188  	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	if len(driverConfig.PortMap) > 1 {
   193  		return nil, fmt.Errorf("Only one port_map block is allowed in the qemu driver config")
   194  	}
   195  
   196  	d.driverConfig = &driverConfig
   197  
   198  	r := NewPrestartResponse()
   199  	if len(driverConfig.PortMap) == 1 {
   200  		r.Network = &cstructs.DriverNetwork{
   201  			PortMap: driverConfig.PortMap[0],
   202  		}
   203  	}
   204  	return r, nil
   205  }
   206  
   207  // Run an existing Qemu image. Start() will pull down an existing, valid Qemu
   208  // image and save it to the Drivers Allocation Dir
   209  func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) {
   210  	// Get the image source
   211  	vmPath := d.driverConfig.ImagePath
   212  	if vmPath == "" {
   213  		return nil, fmt.Errorf("image_path must be set")
   214  	}
   215  	vmID := filepath.Base(vmPath)
   216  
   217  	// Parse configuration arguments
   218  	// Create the base arguments
   219  	accelerator := "tcg"
   220  	if d.driverConfig.Accelerator != "" {
   221  		accelerator = d.driverConfig.Accelerator
   222  	}
   223  
   224  	if task.Resources.MemoryMB < 128 || task.Resources.MemoryMB > 4000000 {
   225  		return nil, fmt.Errorf("Qemu memory assignment out of bounds")
   226  	}
   227  	mem := fmt.Sprintf("%dM", task.Resources.MemoryMB)
   228  
   229  	absPath, err := GetAbsolutePath("qemu-system-x86_64")
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	args := []string{
   235  		absPath,
   236  		"-machine", "type=pc,accel=" + accelerator,
   237  		"-name", vmID,
   238  		"-m", mem,
   239  		"-drive", "file=" + vmPath,
   240  		"-nographic",
   241  	}
   242  
   243  	var monitorPath string
   244  	if d.driverConfig.GracefulShutdown {
   245  		if runtime.GOOS == "windows" {
   246  			return nil, errors.New("QEMU graceful shutdown is unsupported on the Windows platform")
   247  		}
   248  		// This socket will be used to manage the virtual machine (for example,
   249  		// to perform graceful shutdowns)
   250  		monitorPath, err = d.getMonitorPath(ctx.TaskDir.Dir)
   251  		if err != nil {
   252  			d.logger.Printf("[ERR] driver.qemu: could not get qemu monitor path: %s", err)
   253  			return nil, err
   254  		}
   255  		d.logger.Printf("[DEBUG] driver.qemu: got monitor path OK: %s", monitorPath)
   256  		args = append(args, "-monitor", fmt.Sprintf("unix:%s,server,nowait", monitorPath))
   257  	}
   258  
   259  	// Add pass through arguments to qemu executable. A user can specify
   260  	// these arguments in driver task configuration. These arguments are
   261  	// passed directly to the qemu driver as command line options.
   262  	// For example, args = [ "-nodefconfig", "-nodefaults" ]
   263  	// This will allow a VM with embedded configuration to boot successfully.
   264  	args = append(args, d.driverConfig.Args...)
   265  
   266  	// Check the Resources required Networks to add port mappings. If no resources
   267  	// are required, we assume the VM is a purely compute job and does not require
   268  	// the outside world to be able to reach it. VMs ran without port mappings can
   269  	// still reach out to the world, but without port mappings it is effectively
   270  	// firewalled
   271  	protocols := []string{"udp", "tcp"}
   272  	if len(task.Resources.Networks) > 0 && len(d.driverConfig.PortMap) == 1 {
   273  		// Loop through the port map and construct the hostfwd string, to map
   274  		// reserved ports to the ports listenting in the VM
   275  		// Ex: hostfwd=tcp::22000-:22,hostfwd=tcp::80-:8080
   276  		var forwarding []string
   277  		taskPorts := task.Resources.Networks[0].PortLabels()
   278  		for label, guest := range d.driverConfig.PortMap[0] {
   279  			host, ok := taskPorts[label]
   280  			if !ok {
   281  				return nil, fmt.Errorf("Unknown port label %q", label)
   282  			}
   283  
   284  			for _, p := range protocols {
   285  				forwarding = append(forwarding, fmt.Sprintf("hostfwd=%s::%d-:%d", p, host, guest))
   286  			}
   287  		}
   288  
   289  		if len(forwarding) != 0 {
   290  			args = append(args,
   291  				"-netdev",
   292  				fmt.Sprintf("user,id=user.0,%s", strings.Join(forwarding, ",")),
   293  				"-device", "virtio-net,netdev=user.0",
   294  			)
   295  		}
   296  	}
   297  
   298  	// If using KVM, add optimization args
   299  	if accelerator == "kvm" {
   300  		if runtime.GOOS == "windows" {
   301  			return nil, errors.New("KVM accelerator is unsupported on the Windows platform")
   302  		}
   303  		args = append(args,
   304  			"-enable-kvm",
   305  			"-cpu", "host",
   306  			// Do we have cores information available to the Driver?
   307  			// "-smp", fmt.Sprintf("%d", cores),
   308  		)
   309  	}
   310  
   311  	d.logger.Printf("[DEBUG] driver.qemu: starting QemuVM command: %q", strings.Join(args, " "))
   312  	pluginLogFile := filepath.Join(ctx.TaskDir.Dir, "executor.out")
   313  	executorConfig := &dstructs.ExecutorConfig{
   314  		LogFile:  pluginLogFile,
   315  		LogLevel: d.config.LogLevel,
   316  	}
   317  
   318  	exec, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig)
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  	executorCtx := &executor.ExecutorContext{
   323  		TaskEnv: ctx.TaskEnv,
   324  		Driver:  "qemu",
   325  		Task:    task,
   326  		TaskDir: ctx.TaskDir.Dir,
   327  		LogDir:  ctx.TaskDir.LogDir,
   328  	}
   329  	if err := exec.SetContext(executorCtx); err != nil {
   330  		pluginClient.Kill()
   331  		return nil, fmt.Errorf("failed to set executor context: %v", err)
   332  	}
   333  
   334  	execCmd := &executor.ExecCommand{
   335  		Cmd:  args[0],
   336  		Args: args[1:],
   337  		User: task.User,
   338  	}
   339  	ps, err := exec.LaunchCmd(execCmd)
   340  	if err != nil {
   341  		pluginClient.Kill()
   342  		return nil, err
   343  	}
   344  	d.logger.Printf("[INFO] driver.qemu: started new QemuVM: %s", vmID)
   345  
   346  	// Create and Return Handle
   347  	maxKill := d.DriverContext.config.MaxKillTimeout
   348  	h := &qemuHandle{
   349  		pluginClient:   pluginClient,
   350  		executor:       exec,
   351  		userPid:        ps.Pid,
   352  		killTimeout:    GetKillTimeout(task.KillTimeout, maxKill),
   353  		maxKillTimeout: maxKill,
   354  		monitorPath:    monitorPath,
   355  		version:        d.config.Version.VersionNumber(),
   356  		logger:         d.logger,
   357  		doneCh:         make(chan struct{}),
   358  		waitCh:         make(chan *dstructs.WaitResult, 1),
   359  	}
   360  	go h.run()
   361  	resp := &StartResponse{Handle: h}
   362  	if len(d.driverConfig.PortMap) == 1 {
   363  		resp.Network = &cstructs.DriverNetwork{
   364  			PortMap: d.driverConfig.PortMap[0],
   365  		}
   366  	}
   367  	return resp, nil
   368  }
   369  
   370  type qemuId struct {
   371  	Version        string
   372  	KillTimeout    time.Duration
   373  	MaxKillTimeout time.Duration
   374  	UserPid        int
   375  	PluginConfig   *PluginReattachConfig
   376  }
   377  
   378  func (d *QemuDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   379  	id := &qemuId{}
   380  	if err := json.Unmarshal([]byte(handleID), id); err != nil {
   381  		return nil, fmt.Errorf("Failed to parse handle %q: %v", handleID, err)
   382  	}
   383  
   384  	pluginConfig := &plugin.ClientConfig{
   385  		Reattach: id.PluginConfig.PluginConfig(),
   386  	}
   387  
   388  	exec, pluginClient, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput)
   389  	if err != nil {
   390  		d.logger.Printf("[ERR] driver.qemu: error connecting to plugin so destroying plugin pid %d and user pid %d", id.PluginConfig.Pid, id.UserPid)
   391  		if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil {
   392  			d.logger.Printf("[ERR] driver.qemu: error destroying plugin pid %d and userpid %d: %v", id.PluginConfig.Pid, id.UserPid, e)
   393  		}
   394  		return nil, fmt.Errorf("error connecting to plugin: %v", err)
   395  	}
   396  
   397  	ver, _ := exec.Version()
   398  	d.logger.Printf("[DEBUG] driver.qemu: version of executor: %v", ver.Version)
   399  	// Return a driver handle
   400  	h := &qemuHandle{
   401  		pluginClient:   pluginClient,
   402  		executor:       exec,
   403  		userPid:        id.UserPid,
   404  		logger:         d.logger,
   405  		killTimeout:    id.KillTimeout,
   406  		maxKillTimeout: id.MaxKillTimeout,
   407  		version:        id.Version,
   408  		doneCh:         make(chan struct{}),
   409  		waitCh:         make(chan *dstructs.WaitResult, 1),
   410  	}
   411  	go h.run()
   412  	return h, nil
   413  }
   414  
   415  func (d *QemuDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil }
   416  
   417  func (h *qemuHandle) ID() string {
   418  	id := qemuId{
   419  		Version:        h.version,
   420  		KillTimeout:    h.killTimeout,
   421  		MaxKillTimeout: h.maxKillTimeout,
   422  		PluginConfig:   NewPluginReattachConfig(h.pluginClient.ReattachConfig()),
   423  		UserPid:        h.userPid,
   424  	}
   425  
   426  	data, err := json.Marshal(id)
   427  	if err != nil {
   428  		h.logger.Printf("[ERR] driver.qemu: failed to marshal ID to JSON: %s", err)
   429  	}
   430  	return string(data)
   431  }
   432  
   433  func (h *qemuHandle) WaitCh() chan *dstructs.WaitResult {
   434  	return h.waitCh
   435  }
   436  
   437  func (h *qemuHandle) Update(task *structs.Task) error {
   438  	// Store the updated kill timeout.
   439  	h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout)
   440  	h.executor.UpdateTask(task)
   441  
   442  	// Update is not possible
   443  	return nil
   444  }
   445  
   446  func (h *qemuHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) {
   447  	return nil, 0, fmt.Errorf("Qemu driver can't execute commands")
   448  }
   449  
   450  func (h *qemuHandle) Signal(s os.Signal) error {
   451  	return fmt.Errorf("Qemu driver can't send signals")
   452  }
   453  
   454  func (h *qemuHandle) Kill() error {
   455  	gracefulShutdownSent := false
   456  	// Attempt a graceful shutdown only if it was configured in the job
   457  	if h.monitorPath != "" {
   458  		if err := sendQemuShutdown(h.logger, h.monitorPath, h.userPid); err == nil {
   459  			gracefulShutdownSent = true
   460  		} else {
   461  			h.logger.Printf("[DEBUG] driver.qemu: error sending graceful shutdown for user process pid %d: %s", h.userPid, err)
   462  		}
   463  	}
   464  
   465  	// If Nomad did not send a graceful shutdown signal, issue an interrupt to
   466  	// the qemu process as a last resort
   467  	if gracefulShutdownSent == false {
   468  		h.logger.Printf("[DEBUG] driver.qemu: graceful shutdown is not enabled, sending an interrupt signal to pid: %d", h.userPid)
   469  		if err := h.executor.ShutDown(); err != nil {
   470  			if h.pluginClient.Exited() {
   471  				return nil
   472  			}
   473  			return fmt.Errorf("executor Shutdown failed: %v", err)
   474  		}
   475  	}
   476  
   477  	// If the qemu process exits before the kill timeout is reached, doneChan
   478  	// will close and we'll exit without an error. If it takes too long, the
   479  	// timer will fire and we'll attempt to kill the process.
   480  	select {
   481  	case <-h.doneCh:
   482  		return nil
   483  	case <-time.After(h.killTimeout):
   484  		h.logger.Printf("[DEBUG] driver.qemu: kill timeout of %s exceeded for user process pid %d", h.killTimeout.String(), h.userPid)
   485  
   486  		if h.pluginClient.Exited() {
   487  			return nil
   488  		}
   489  		if err := h.executor.Exit(); err != nil {
   490  			return fmt.Errorf("executor Exit failed: %v", err)
   491  		}
   492  		return nil
   493  	}
   494  }
   495  
   496  func (h *qemuHandle) Stats() (*cstructs.TaskResourceUsage, error) {
   497  	return h.executor.Stats()
   498  }
   499  
   500  func (h *qemuHandle) run() {
   501  	ps, werr := h.executor.Wait()
   502  	if ps.ExitCode == 0 && werr != nil {
   503  		if e := killProcess(h.userPid); e != nil {
   504  			h.logger.Printf("[ERR] driver.qemu: error killing user process pid %d: %v", h.userPid, e)
   505  		}
   506  	}
   507  	close(h.doneCh)
   508  
   509  	// Exit the executor
   510  	h.executor.Exit()
   511  	h.pluginClient.Kill()
   512  
   513  	// Send the results
   514  	h.waitCh <- &dstructs.WaitResult{ExitCode: ps.ExitCode, Signal: ps.Signal, Err: werr}
   515  	close(h.waitCh)
   516  }
   517  
   518  // sendQemuShutdown attempts to issue an ACPI power-off command via the qemu
   519  // monitor
   520  func sendQemuShutdown(logger *log.Logger, monitorPath string, userPid int) error {
   521  	if monitorPath == "" {
   522  		return errors.New("monitorPath not set")
   523  	}
   524  	monitorSocket, err := net.Dial("unix", monitorPath)
   525  	if err != nil {
   526  		logger.Printf("[WARN] driver.qemu: could not connect to qemu monitor %q for user process pid %d: %s", monitorPath, userPid, err)
   527  		return err
   528  	}
   529  	defer monitorSocket.Close()
   530  	logger.Printf("[DEBUG] driver.qemu: sending graceful shutdown command to qemu monitor socket %q for user process pid %d", monitorPath, userPid)
   531  	_, err = monitorSocket.Write([]byte(qemuGracefulShutdownMsg))
   532  	if err != nil {
   533  		logger.Printf("[WARN] driver.qemu: failed to send shutdown message %q to monitor socket %q for user process pid %d: %s", qemuGracefulShutdownMsg, monitorPath, userPid, err)
   534  	}
   535  	return err
   536  }