github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/client/driver/qemu.go (about)

     1  package driver
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/hashicorp/go-plugin"
    16  	"github.com/hashicorp/nomad/client/config"
    17  	"github.com/hashicorp/nomad/client/driver/executor"
    18  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    19  	"github.com/hashicorp/nomad/client/fingerprint"
    20  	cstructs "github.com/hashicorp/nomad/client/structs"
    21  	"github.com/hashicorp/nomad/helper/fields"
    22  	"github.com/hashicorp/nomad/nomad/structs"
    23  	"github.com/mitchellh/mapstructure"
    24  )
    25  
    26  var (
    27  	reQemuVersion = regexp.MustCompile(`version (\d[\.\d+]+)`)
    28  )
    29  
    30  const (
    31  	// The key populated in Node Attributes to indicate presence of the Qemu
    32  	// driver
    33  	qemuDriverAttr = "driver.qemu"
    34  )
    35  
    36  // QemuDriver is a driver for running images via Qemu
    37  // We attempt to chose sane defaults for now, with more configuration available
    38  // planned in the future
    39  type QemuDriver struct {
    40  	DriverContext
    41  	fingerprint.StaticFingerprinter
    42  }
    43  
    44  type QemuDriverConfig struct {
    45  	ImagePath   string           `mapstructure:"image_path"`
    46  	Accelerator string           `mapstructure:"accelerator"`
    47  	PortMap     []map[string]int `mapstructure:"port_map"` // A map of host port labels and to guest ports.
    48  	Args        []string         `mapstructure:"args"`     // extra arguments to qemu executable
    49  }
    50  
    51  // qemuHandle is returned from Start/Open as a handle to the PID
    52  type qemuHandle struct {
    53  	pluginClient   *plugin.Client
    54  	userPid        int
    55  	executor       executor.Executor
    56  	killTimeout    time.Duration
    57  	maxKillTimeout time.Duration
    58  	logger         *log.Logger
    59  	version        string
    60  	waitCh         chan *dstructs.WaitResult
    61  	doneCh         chan struct{}
    62  }
    63  
    64  // NewQemuDriver is used to create a new exec driver
    65  func NewQemuDriver(ctx *DriverContext) Driver {
    66  	return &QemuDriver{DriverContext: *ctx}
    67  }
    68  
    69  // Validate is used to validate the driver configuration
    70  func (d *QemuDriver) Validate(config map[string]interface{}) error {
    71  	fd := &fields.FieldData{
    72  		Raw: config,
    73  		Schema: map[string]*fields.FieldSchema{
    74  			"image_path": &fields.FieldSchema{
    75  				Type:     fields.TypeString,
    76  				Required: true,
    77  			},
    78  			"accelerator": &fields.FieldSchema{
    79  				Type: fields.TypeString,
    80  			},
    81  			"port_map": &fields.FieldSchema{
    82  				Type: fields.TypeArray,
    83  			},
    84  			"args": &fields.FieldSchema{
    85  				Type: fields.TypeArray,
    86  			},
    87  		},
    88  	}
    89  
    90  	if err := fd.Validate(); err != nil {
    91  		return err
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  func (d *QemuDriver) Abilities() DriverAbilities {
    98  	return DriverAbilities{
    99  		SendSignals: false,
   100  	}
   101  }
   102  
   103  func (d *QemuDriver) FSIsolation() cstructs.FSIsolation {
   104  	return cstructs.FSIsolationImage
   105  }
   106  
   107  func (d *QemuDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
   108  	bin := "qemu-system-x86_64"
   109  	if runtime.GOOS == "windows" {
   110  		// On windows, the "qemu-system-x86_64" command does not respond to the
   111  		// version flag.
   112  		bin = "qemu-img"
   113  	}
   114  	outBytes, err := exec.Command(bin, "--version").Output()
   115  	if err != nil {
   116  		delete(node.Attributes, qemuDriverAttr)
   117  		return false, nil
   118  	}
   119  	out := strings.TrimSpace(string(outBytes))
   120  
   121  	matches := reQemuVersion.FindStringSubmatch(out)
   122  	if len(matches) != 2 {
   123  		delete(node.Attributes, qemuDriverAttr)
   124  		return false, fmt.Errorf("Unable to parse Qemu version string: %#v", matches)
   125  	}
   126  
   127  	node.Attributes[qemuDriverAttr] = "1"
   128  	node.Attributes["driver.qemu.version"] = matches[1]
   129  	return true, nil
   130  }
   131  
   132  func (d *QemuDriver) Prestart(*ExecContext, *structs.Task) (*CreatedResources, error) {
   133  	return nil, nil
   134  }
   135  
   136  // Run an existing Qemu image. Start() will pull down an existing, valid Qemu
   137  // image and save it to the Drivers Allocation Dir
   138  func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
   139  	var driverConfig QemuDriverConfig
   140  	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	if len(driverConfig.PortMap) > 1 {
   145  		return nil, fmt.Errorf("Only one port_map block is allowed in the qemu driver config")
   146  	}
   147  
   148  	// Get the image source
   149  	vmPath := driverConfig.ImagePath
   150  	if vmPath == "" {
   151  		return nil, fmt.Errorf("image_path must be set")
   152  	}
   153  	vmID := filepath.Base(vmPath)
   154  
   155  	// Parse configuration arguments
   156  	// Create the base arguments
   157  	accelerator := "tcg"
   158  	if driverConfig.Accelerator != "" {
   159  		accelerator = driverConfig.Accelerator
   160  	}
   161  	// TODO: Check a lower bounds, e.g. the default 128 of Qemu
   162  	mem := fmt.Sprintf("%dM", task.Resources.MemoryMB)
   163  
   164  	absPath, err := GetAbsolutePath("qemu-system-x86_64")
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	args := []string{
   170  		absPath,
   171  		"-machine", "type=pc,accel=" + accelerator,
   172  		"-name", vmID,
   173  		"-m", mem,
   174  		"-drive", "file=" + vmPath,
   175  		"-nographic",
   176  	}
   177  
   178  	// Add pass through arguments to qemu executable. A user can specify
   179  	// these arguments in driver task configuration. These arguments are
   180  	// passed directly to the qemu driver as command line options.
   181  	// For example, args = [ "-nodefconfig", "-nodefaults" ]
   182  	// This will allow a VM with embedded configuration to boot successfully.
   183  	args = append(args, driverConfig.Args...)
   184  
   185  	// Check the Resources required Networks to add port mappings. If no resources
   186  	// are required, we assume the VM is a purely compute job and does not require
   187  	// the outside world to be able to reach it. VMs ran without port mappings can
   188  	// still reach out to the world, but without port mappings it is effectively
   189  	// firewalled
   190  	protocols := []string{"udp", "tcp"}
   191  	if len(task.Resources.Networks) > 0 && len(driverConfig.PortMap) == 1 {
   192  		// Loop through the port map and construct the hostfwd string, to map
   193  		// reserved ports to the ports listenting in the VM
   194  		// Ex: hostfwd=tcp::22000-:22,hostfwd=tcp::80-:8080
   195  		var forwarding []string
   196  		taskPorts := task.Resources.Networks[0].MapLabelToValues(nil)
   197  		for label, guest := range driverConfig.PortMap[0] {
   198  			host, ok := taskPorts[label]
   199  			if !ok {
   200  				return nil, fmt.Errorf("Unknown port label %q", label)
   201  			}
   202  
   203  			for _, p := range protocols {
   204  				forwarding = append(forwarding, fmt.Sprintf("hostfwd=%s::%d-:%d", p, host, guest))
   205  			}
   206  		}
   207  
   208  		if len(forwarding) != 0 {
   209  			args = append(args,
   210  				"-netdev",
   211  				fmt.Sprintf("user,id=user.0,%s", strings.Join(forwarding, ",")),
   212  				"-device", "virtio-net,netdev=user.0",
   213  			)
   214  		}
   215  	}
   216  
   217  	// If using KVM, add optimization args
   218  	if accelerator == "kvm" {
   219  		args = append(args,
   220  			"-enable-kvm",
   221  			"-cpu", "host",
   222  			// Do we have cores information available to the Driver?
   223  			// "-smp", fmt.Sprintf("%d", cores),
   224  		)
   225  	}
   226  
   227  	d.logger.Printf("[DEBUG] Starting QemuVM command: %q", strings.Join(args, " "))
   228  	pluginLogFile := filepath.Join(ctx.TaskDir.Dir, "executor.out")
   229  	executorConfig := &dstructs.ExecutorConfig{
   230  		LogFile:  pluginLogFile,
   231  		LogLevel: d.config.LogLevel,
   232  	}
   233  
   234  	exec, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	executorCtx := &executor.ExecutorContext{
   239  		TaskEnv: d.taskEnv,
   240  		Driver:  "qemu",
   241  		AllocID: d.DriverContext.allocID,
   242  		Task:    task,
   243  		TaskDir: ctx.TaskDir.Dir,
   244  		LogDir:  ctx.TaskDir.LogDir,
   245  	}
   246  	if err := exec.SetContext(executorCtx); err != nil {
   247  		pluginClient.Kill()
   248  		return nil, fmt.Errorf("failed to set executor context: %v", err)
   249  	}
   250  
   251  	execCmd := &executor.ExecCommand{
   252  		Cmd:  args[0],
   253  		Args: args[1:],
   254  		User: task.User,
   255  	}
   256  	ps, err := exec.LaunchCmd(execCmd)
   257  	if err != nil {
   258  		pluginClient.Kill()
   259  		return nil, err
   260  	}
   261  	d.logger.Printf("[INFO] Started new QemuVM: %s", vmID)
   262  
   263  	// Create and Return Handle
   264  	maxKill := d.DriverContext.config.MaxKillTimeout
   265  	h := &qemuHandle{
   266  		pluginClient:   pluginClient,
   267  		executor:       exec,
   268  		userPid:        ps.Pid,
   269  		killTimeout:    GetKillTimeout(task.KillTimeout, maxKill),
   270  		maxKillTimeout: maxKill,
   271  		version:        d.config.Version,
   272  		logger:         d.logger,
   273  		doneCh:         make(chan struct{}),
   274  		waitCh:         make(chan *dstructs.WaitResult, 1),
   275  	}
   276  
   277  	if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil {
   278  		h.logger.Printf("[ERR] driver.qemu: error registering services for task: %q: %v", task.Name, err)
   279  	}
   280  	go h.run()
   281  	return h, nil
   282  }
   283  
   284  type qemuId struct {
   285  	Version        string
   286  	KillTimeout    time.Duration
   287  	MaxKillTimeout time.Duration
   288  	UserPid        int
   289  	PluginConfig   *PluginReattachConfig
   290  }
   291  
   292  func (d *QemuDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   293  	id := &qemuId{}
   294  	if err := json.Unmarshal([]byte(handleID), id); err != nil {
   295  		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
   296  	}
   297  
   298  	pluginConfig := &plugin.ClientConfig{
   299  		Reattach: id.PluginConfig.PluginConfig(),
   300  	}
   301  
   302  	exec, pluginClient, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput)
   303  	if err != nil {
   304  		d.logger.Println("[ERR] driver.qemu: error connecting to plugin so destroying plugin pid and user pid")
   305  		if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil {
   306  			d.logger.Printf("[ERR] driver.qemu: error destroying plugin and userpid: %v", e)
   307  		}
   308  		return nil, fmt.Errorf("error connecting to plugin: %v", err)
   309  	}
   310  
   311  	ver, _ := exec.Version()
   312  	d.logger.Printf("[DEBUG] driver.qemu: version of executor: %v", ver.Version)
   313  	// Return a driver handle
   314  	h := &qemuHandle{
   315  		pluginClient:   pluginClient,
   316  		executor:       exec,
   317  		userPid:        id.UserPid,
   318  		logger:         d.logger,
   319  		killTimeout:    id.KillTimeout,
   320  		maxKillTimeout: id.MaxKillTimeout,
   321  		version:        id.Version,
   322  		doneCh:         make(chan struct{}),
   323  		waitCh:         make(chan *dstructs.WaitResult, 1),
   324  	}
   325  	if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil {
   326  		h.logger.Printf("[ERR] driver.qemu: error registering services: %v", err)
   327  	}
   328  	go h.run()
   329  	return h, nil
   330  }
   331  
   332  func (d *QemuDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil }
   333  
   334  func (h *qemuHandle) ID() string {
   335  	id := qemuId{
   336  		Version:        h.version,
   337  		KillTimeout:    h.killTimeout,
   338  		MaxKillTimeout: h.maxKillTimeout,
   339  		PluginConfig:   NewPluginReattachConfig(h.pluginClient.ReattachConfig()),
   340  		UserPid:        h.userPid,
   341  	}
   342  
   343  	data, err := json.Marshal(id)
   344  	if err != nil {
   345  		h.logger.Printf("[ERR] driver.qemu: failed to marshal ID to JSON: %s", err)
   346  	}
   347  	return string(data)
   348  }
   349  
   350  func (h *qemuHandle) WaitCh() chan *dstructs.WaitResult {
   351  	return h.waitCh
   352  }
   353  
   354  func (h *qemuHandle) Update(task *structs.Task) error {
   355  	// Store the updated kill timeout.
   356  	h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout)
   357  	h.executor.UpdateTask(task)
   358  
   359  	// Update is not possible
   360  	return nil
   361  }
   362  
   363  func (h *qemuHandle) Signal(s os.Signal) error {
   364  	return fmt.Errorf("Qemu driver can't send signals")
   365  }
   366  
   367  // TODO: allow a 'shutdown_command' that can be executed over a ssh connection
   368  // to the VM
   369  func (h *qemuHandle) Kill() error {
   370  	if err := h.executor.ShutDown(); err != nil {
   371  		if h.pluginClient.Exited() {
   372  			return nil
   373  		}
   374  		return fmt.Errorf("executor Shutdown failed: %v", err)
   375  	}
   376  
   377  	select {
   378  	case <-h.doneCh:
   379  		return nil
   380  	case <-time.After(h.killTimeout):
   381  		if h.pluginClient.Exited() {
   382  			return nil
   383  		}
   384  		if err := h.executor.Exit(); err != nil {
   385  			return fmt.Errorf("executor Exit failed: %v", err)
   386  		}
   387  
   388  		return nil
   389  	}
   390  }
   391  
   392  func (h *qemuHandle) Stats() (*cstructs.TaskResourceUsage, error) {
   393  	return h.executor.Stats()
   394  }
   395  
   396  func (h *qemuHandle) run() {
   397  	ps, werr := h.executor.Wait()
   398  	if ps.ExitCode == 0 && werr != nil {
   399  		if e := killProcess(h.userPid); e != nil {
   400  			h.logger.Printf("[ERR] driver.qemu: error killing user process: %v", e)
   401  		}
   402  	}
   403  	close(h.doneCh)
   404  
   405  	// Remove services
   406  	if err := h.executor.DeregisterServices(); err != nil {
   407  		h.logger.Printf("[ERR] driver.qemu: failed to deregister services: %v", err)
   408  	}
   409  
   410  	// Exit the executor
   411  	h.executor.Exit()
   412  	h.pluginClient.Kill()
   413  
   414  	// Send the results
   415  	h.waitCh <- &dstructs.WaitResult{ExitCode: ps.ExitCode, Signal: ps.Signal, Err: werr}
   416  	close(h.waitCh)
   417  }