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 }