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