diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index b9ea394db..a9a021e35 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -117,6 +117,8 @@ type Config struct { OutputDir string `mapstructure:"output_directory"` QemuArgs [][]string `mapstructure:"qemuargs"` QemuBinary string `mapstructure:"qemu_binary"` + QMPEnable bool `mapstructure:"qmp_enable"` + QMPSocketPath string `mapstructure:"qmp_socket_path"` ShutdownCommand string `mapstructure:"shutdown_command"` SSHHostPortMin int `mapstructure:"ssh_host_port_min"` SSHHostPortMax int `mapstructure:"ssh_host_port_max"` @@ -344,6 +346,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend( errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max")) } + if b.config.SSHHostPortMin < 0 { errs = packer.MultiErrorAppend( errs, errors.New("ssh_host_port_min must be positive")) @@ -354,6 +357,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) } + if b.config.QMPEnable && b.config.QMPSocketPath == "" { + socketName := fmt.Sprintf("%s.monitor", b.config.VMName) + b.config.QMPSocketPath = filepath.Join(b.config.OutputDir, socketName) + } + if b.config.QemuArgs == nil { b.config.QemuArgs = make([][]string, 0) } @@ -425,6 +433,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack steps = append(steps, new(stepConfigureVNC), steprun, + new(stepConfigureQMP), &stepTypeBootCommand{}, ) diff --git a/builder/qemu/builder_test.go b/builder/qemu/builder_test.go index e726ce456..a064c1265 100644 --- a/builder/qemu/builder_test.go +++ b/builder/qemu/builder_test.go @@ -598,3 +598,25 @@ func TestBuilderPrepare_QemuArgs(t *testing.T) { t.Fatalf("bad: %#v", b.config.QemuArgs) } } + +func TestBuilderPrepare_QMP(t *testing.T) { + var b Builder + config := testConfig() + + // QMP Defaults + config["qmp_enable"] = true + config["output_directory"] = "not-a-real-directory" + b = Builder{} + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + expected := "not-a-real-directory/packer-foo.monitor" + if !reflect.DeepEqual(b.config.QMPSocketPath, expected) { + t.Fatalf("Bad QMP socket Path: %s", b.config.QMPSocketPath) + } +} diff --git a/builder/qemu/step_configure_qmp.go b/builder/qemu/step_configure_qmp.go new file mode 100644 index 000000000..9afbb865e --- /dev/null +++ b/builder/qemu/step_configure_qmp.go @@ -0,0 +1,56 @@ +package qemu + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/digitalocean/go-qemu/qmp" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" +) + +// This step configures the VM to enable the QMP listener. +// +// Uses: +// config *config +// ui packer.Ui +// +// Produces: +type stepConfigureQMP struct { + monitor *qmp.SocketMonitor +} + +func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + ui := state.Get("ui").(packer.Ui) + + if !config.QMPEnable { + return multistep.ActionContinue + } + + msg := fmt.Sprintf("Opening QMP socket at: %s", config.QMPSocketPath) + ui.Say(msg) + log.Print(msg) + + // Open QMP socket + var err error + s.monitor, err = qmp.NewSocketMonitor("unix", config.QMPSocketPath, 2*time.Second) + if err != nil { + err := fmt.Errorf("Error opening QMP socket: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + QMPMonitor := s.monitor + + log.Printf("QMP socket open SUCCESS") + + state.Put("qmp_monitor", QMPMonitor) + + return multistep.ActionContinue +} + +func (s *stepConfigureQMP) Cleanup(multistep.StateBag) { +} diff --git a/builder/qemu/step_run.go b/builder/qemu/step_run.go index de22a94b8..19b168aad 100644 --- a/builder/qemu/step_run.go +++ b/builder/qemu/step_run.go @@ -85,6 +85,10 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0") } + if config.QMPEnable { + defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath) + } + rawVersion, err := driver.Version() if err != nil { return nil, err diff --git a/website/source/docs/builders/qemu.html.md.erb b/website/source/docs/builders/qemu.html.md.erb index 251aab8e4..f9ab3de9a 100644 --- a/website/source/docs/builders/qemu.html.md.erb +++ b/website/source/docs/builders/qemu.html.md.erb @@ -282,6 +282,13 @@ Linux server and have not enabled X11 forwarding (`ssh -X`). switch/value pairs. Any value specified as an empty string is ignored. All values after the switch are concatenated with no separator. +- `qmp_enable` (bool) - Enable QMP socket. Location is specified by + `qmp_socket_path`. + Defaults to false. + +- `qmp_socket_path` (string) - QMP Socket Path when `qmp_enable` is true. + Defaults to `output_directory`/`vm_name`.monitor. + ~> **Warning:** The qemu command line allows extreme flexibility, so beware of conflicting arguments causing failures of your run. For instance, using --no-acpi could break the ability to send power signal type commands (e.g.,