github.com/huiliang/nomad@v0.2.1-0.20151124023127-7a8b664699ff/client/driver/qemu.go (about)

     1  package driver
     2  
     3  import (
     4  	"fmt"
     5  	"os/exec"
     6  	"path/filepath"
     7  	"regexp"
     8  	"runtime"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/hashicorp/nomad/client/allocdir"
    13  	"github.com/hashicorp/nomad/client/config"
    14  	"github.com/hashicorp/nomad/client/driver/executor"
    15  	cstructs "github.com/hashicorp/nomad/client/driver/structs"
    16  	"github.com/hashicorp/nomad/client/fingerprint"
    17  	"github.com/hashicorp/nomad/client/getter"
    18  	"github.com/hashicorp/nomad/nomad/structs"
    19  	"github.com/mitchellh/mapstructure"
    20  )
    21  
    22  var (
    23  	reQemuVersion = regexp.MustCompile(`version (\d[\.\d+]+)`)
    24  )
    25  
    26  // QemuDriver is a driver for running images via Qemu
    27  // We attempt to chose sane defaults for now, with more configuration available
    28  // planned in the future
    29  type QemuDriver struct {
    30  	DriverContext
    31  	fingerprint.StaticFingerprinter
    32  }
    33  
    34  type QemuDriverConfig struct {
    35  	ArtifactSource string           `mapstructure:"artifact_source"`
    36  	Checksum       string           `mapstructure:"checksum"`
    37  	Accelerator    string           `mapstructure:"accelerator"`
    38  	PortMap        []map[string]int `mapstructure:"port_map"` // A map of host port labels and to guest ports.
    39  }
    40  
    41  // qemuHandle is returned from Start/Open as a handle to the PID
    42  type qemuHandle struct {
    43  	cmd    executor.Executor
    44  	waitCh chan *cstructs.WaitResult
    45  	doneCh chan struct{}
    46  }
    47  
    48  // NewQemuDriver is used to create a new exec driver
    49  func NewQemuDriver(ctx *DriverContext) Driver {
    50  	return &QemuDriver{DriverContext: *ctx}
    51  }
    52  
    53  func (d *QemuDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
    54  	bin := "qemu-system-x86_64"
    55  	if runtime.GOOS == "windows" {
    56  		// On windows, the "qemu-system-x86_64" command does not respond to the
    57  		// version flag.
    58  		bin = "qemu-img"
    59  	}
    60  	outBytes, err := exec.Command(bin, "--version").Output()
    61  	if err != nil {
    62  		return false, nil
    63  	}
    64  	out := strings.TrimSpace(string(outBytes))
    65  
    66  	matches := reQemuVersion.FindStringSubmatch(out)
    67  	if len(matches) != 2 {
    68  		return false, fmt.Errorf("Unable to parse Qemu version string: %#v", matches)
    69  	}
    70  
    71  	node.Attributes["driver.qemu"] = "1"
    72  	node.Attributes["driver.qemu.version"] = matches[1]
    73  
    74  	return true, nil
    75  }
    76  
    77  // Run an existing Qemu image. Start() will pull down an existing, valid Qemu
    78  // image and save it to the Drivers Allocation Dir
    79  func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
    80  	var driverConfig QemuDriverConfig
    81  	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	if len(driverConfig.PortMap) > 1 {
    86  		return nil, fmt.Errorf("Only one port_map block is allowed in the qemu driver config")
    87  	}
    88  
    89  	// Get the image source
    90  	source, ok := task.Config["artifact_source"]
    91  	if !ok || source == "" {
    92  		return nil, fmt.Errorf("Missing source image Qemu driver")
    93  	}
    94  
    95  	// Qemu defaults to 128M of RAM for a given VM. Instead, we force users to
    96  	// supply a memory size in the tasks resources
    97  	if task.Resources == nil || task.Resources.MemoryMB == 0 {
    98  		return nil, fmt.Errorf("Missing required Task Resource: Memory")
    99  	}
   100  
   101  	// Get the tasks local directory.
   102  	taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName]
   103  	if !ok {
   104  		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
   105  	}
   106  
   107  	// Proceed to download an artifact to be executed.
   108  	vmPath, err := getter.GetArtifact(
   109  		filepath.Join(taskDir, allocdir.TaskLocal),
   110  		driverConfig.ArtifactSource,
   111  		driverConfig.Checksum,
   112  		d.logger,
   113  	)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	vmID := filepath.Base(vmPath)
   119  
   120  	// Parse configuration arguments
   121  	// Create the base arguments
   122  	accelerator := "tcg"
   123  	if driverConfig.Accelerator != "" {
   124  		accelerator = driverConfig.Accelerator
   125  	}
   126  	// TODO: Check a lower bounds, e.g. the default 128 of Qemu
   127  	mem := fmt.Sprintf("%dM", task.Resources.MemoryMB)
   128  
   129  	args := []string{
   130  		"qemu-system-x86_64",
   131  		"-machine", "type=pc,accel=" + accelerator,
   132  		"-name", vmID,
   133  		"-m", mem,
   134  		"-drive", "file=" + vmPath,
   135  		"-nodefconfig",
   136  		"-nodefaults",
   137  		"-nographic",
   138  	}
   139  
   140  	// Check the Resources required Networks to add port mappings. If no resources
   141  	// are required, we assume the VM is a purely compute job and does not require
   142  	// the outside world to be able to reach it. VMs ran without port mappings can
   143  	// still reach out to the world, but without port mappings it is effectively
   144  	// firewalled
   145  	protocols := []string{"udp", "tcp"}
   146  	if len(task.Resources.Networks) > 0 && len(driverConfig.PortMap) == 1 {
   147  		// Loop through the port map and construct the hostfwd string, to map
   148  		// reserved ports to the ports listenting in the VM
   149  		// Ex: hostfwd=tcp::22000-:22,hostfwd=tcp::80-:8080
   150  		var forwarding []string
   151  		taskPorts := task.Resources.Networks[0].MapLabelToValues(nil)
   152  		for label, guest := range driverConfig.PortMap[0] {
   153  			host, ok := taskPorts[label]
   154  			if !ok {
   155  				return nil, fmt.Errorf("Unknown port label %q", label)
   156  			}
   157  
   158  			for _, p := range protocols {
   159  				forwarding = append(forwarding, fmt.Sprintf("hostfwd=%s::%d-:%d", p, host, guest))
   160  			}
   161  		}
   162  
   163  		if len(forwarding) != 0 {
   164  			args = append(args,
   165  				"-netdev",
   166  				fmt.Sprintf("user,id=user.0%s", strings.Join(forwarding, ",")),
   167  				"-device", "virtio-net,netdev=user.0",
   168  			)
   169  		}
   170  	}
   171  
   172  	// If using KVM, add optimization args
   173  	if accelerator == "kvm" {
   174  		args = append(args,
   175  			"-enable-kvm",
   176  			"-cpu", "host",
   177  			// Do we have cores information available to the Driver?
   178  			// "-smp", fmt.Sprintf("%d", cores),
   179  		)
   180  	}
   181  
   182  	// Setup the command
   183  	cmd := executor.Command(args[0], args[1:]...)
   184  	if err := cmd.Limit(task.Resources); err != nil {
   185  		return nil, fmt.Errorf("failed to constrain resources: %s", err)
   186  	}
   187  
   188  	if err := cmd.ConfigureTaskDir(d.taskName, ctx.AllocDir); err != nil {
   189  		return nil, fmt.Errorf("failed to configure task directory: %v", err)
   190  	}
   191  
   192  	d.logger.Printf("[DEBUG] Starting QemuVM command: %q", strings.Join(args, " "))
   193  	if err := cmd.Start(); err != nil {
   194  		return nil, fmt.Errorf("failed to start command: %v", err)
   195  	}
   196  	d.logger.Printf("[INFO] Started new QemuVM: %s", vmID)
   197  
   198  	// Create and Return Handle
   199  	h := &qemuHandle{
   200  		cmd:    cmd,
   201  		doneCh: make(chan struct{}),
   202  		waitCh: make(chan *cstructs.WaitResult, 1),
   203  	}
   204  
   205  	go h.run()
   206  	return h, nil
   207  }
   208  
   209  func (d *QemuDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   210  	// Find the process
   211  	cmd, err := executor.OpenId(handleID)
   212  	if err != nil {
   213  		return nil, fmt.Errorf("failed to open ID %v: %v", handleID, err)
   214  	}
   215  
   216  	// Return a driver handle
   217  	h := &execHandle{
   218  		cmd:    cmd,
   219  		doneCh: make(chan struct{}),
   220  		waitCh: make(chan *cstructs.WaitResult, 1),
   221  	}
   222  	go h.run()
   223  	return h, nil
   224  }
   225  
   226  func (h *qemuHandle) ID() string {
   227  	id, _ := h.cmd.ID()
   228  	return id
   229  }
   230  
   231  func (h *qemuHandle) WaitCh() chan *cstructs.WaitResult {
   232  	return h.waitCh
   233  }
   234  
   235  func (h *qemuHandle) Update(task *structs.Task) error {
   236  	// Update is not possible
   237  	return nil
   238  }
   239  
   240  // TODO: allow a 'shutdown_command' that can be executed over a ssh connection
   241  // to the VM
   242  func (h *qemuHandle) Kill() error {
   243  	h.cmd.Shutdown()
   244  	select {
   245  	case <-h.doneCh:
   246  		return nil
   247  	case <-time.After(5 * time.Second):
   248  		return h.cmd.ForceStop()
   249  	}
   250  }
   251  
   252  func (h *qemuHandle) run() {
   253  	res := h.cmd.Wait()
   254  	close(h.doneCh)
   255  	h.waitCh <- res
   256  	close(h.waitCh)
   257  }