aboutsummaryrefslogtreecommitdiff
path: root/pkg/machine/qemu
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/machine/qemu')
-rw-r--r--pkg/machine/qemu/config.go4
-rw-r--r--pkg/machine/qemu/machine.go139
-rw-r--r--pkg/machine/qemu/options_darwin_arm64.go23
3 files changed, 126 insertions, 40 deletions
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index 56c95e3b3..bada1af9b 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -72,8 +72,10 @@ type MachineVM struct {
Mounts []machine.Mount
// Name of VM
Name string
- // PidFilePath is the where the PID file lives
+ // PidFilePath is the where the Proxy PID file lives
PidFilePath machine.VMFile
+ // VMPidFilePath is the where the VM PID file lives
+ VMPidFilePath machine.VMFile
// QMPMonitor is the qemu monitor object for sending commands
QMPMonitor Monitor
// ReadySocket tells host when vm is booted
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index 1b0d63986..2fe0230cf 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -16,9 +16,11 @@ import (
"net/url"
"os"
"os/exec"
+ "os/signal"
"path/filepath"
"strconv"
"strings"
+ "syscall"
"time"
"github.com/containers/common/pkg/config"
@@ -30,6 +32,7 @@ import (
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
)
var (
@@ -105,6 +108,9 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
if err != nil {
return nil, err
}
+ if err := vm.setPIDSocket(); err != nil {
+ return nil, err
+ }
cmd := []string{execPath}
// Add memory
cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...)
@@ -133,11 +139,9 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
"-device", "virtio-serial",
// qemu needs to establish the long name; other connections can use the symlink'd
"-chardev", "socket,path=" + vm.ReadySocket.Path + ",server=on,wait=off,id=" + vm.Name + "_ready",
- "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...)
+ "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0",
+ "-pidfile", vm.VMPidFilePath.GetPath()}...)
vm.CmdLine = cmd
- if err := vm.setPIDSocket(); err != nil {
- return nil, err
- }
return vm, nil
}
@@ -207,7 +211,7 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error {
vm.Rootful = old.Rootful
vm.UID = old.UID
- // Backup the original config file
+ // Back up the original config file
if err := os.Rename(configPath, configPath+".orig"); err != nil {
return err
}
@@ -484,12 +488,26 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if err := v.writeConfig(); err != nil {
return fmt.Errorf("writing JSON file: %w", err)
}
- defer func() {
+ doneStarting := func() {
v.Starting = false
if err := v.writeConfig(); err != nil {
logrus.Errorf("Writing JSON file: %v", err)
}
+ }
+ defer doneStarting()
+
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt, syscall.SIGTERM)
+ go func() {
+ _, ok := <-c
+ if !ok {
+ return
+ }
+ doneStarting()
+ os.Exit(1)
}()
+ defer close(c)
+
if v.isIncompatible() {
logrus.Errorf("machine %q is incompatible with this release of podman and needs to be recreated, starting for recovery only", v.Name)
}
@@ -564,7 +582,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if !errors.Is(err, os.ErrNotExist) {
return err
}
- // lookup qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394
+ // look up qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394
cfg, err := config.Default()
if err != nil {
return err
@@ -737,17 +755,17 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
if _, err := os.Stat(v.PidFilePath.GetPath()); os.IsNotExist(err) {
return nil
}
- pidString, err := v.PidFilePath.Read()
+ proxyPidString, err := v.PidFilePath.Read()
if err != nil {
return err
}
- pidNum, err := strconv.Atoi(string(pidString))
+ proxyPid, err := strconv.Atoi(string(proxyPidString))
if err != nil {
return err
}
- p, err := os.FindProcess(pidNum)
- if p == nil && err != nil {
+ proxyProc, err := os.FindProcess(proxyPid)
+ if proxyProc == nil && err != nil {
return err
}
@@ -756,7 +774,7 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
return err
}
// Kill the process
- if err := p.Kill(); err != nil {
+ if err := proxyProc.Kill(); err != nil {
return err
}
// Remove the pidfile
@@ -770,24 +788,52 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
if err := qmpMonitor.Disconnect(); err != nil {
// FIXME: this error should probably be returned
- return nil // nolint: nilerr
+ return nil //nolint: nilerr
}
-
disconnected = true
- waitInternal := 250 * time.Millisecond
- for i := 0; i < 5; i++ {
- state, err := v.State(false)
- if err != nil {
- return err
- }
- if state != machine.Running {
- break
+
+ if err := v.ReadySocket.Delete(); err != nil {
+ return err
+ }
+
+ if v.VMPidFilePath.GetPath() == "" {
+ // no vm pid file path means it's probably a machine created before we
+ // started using it, so we revert to the old way of waiting for the
+ // machine to stop
+ fmt.Println("Waiting for VM to stop running...")
+ waitInternal := 250 * time.Millisecond
+ for i := 0; i < 5; i++ {
+ state, err := v.State(false)
+ if err != nil {
+ return err
+ }
+ if state != machine.Running {
+ break
+ }
+ time.Sleep(waitInternal)
+ waitInternal *= 2
}
- time.Sleep(waitInternal)
- waitInternal *= 2
+ // after the machine stops running it normally takes about 1 second for the
+ // qemu VM to exit so we wait a bit to try to avoid issues
+ time.Sleep(2 * time.Second)
+ return nil
+ }
+
+ vmPidString, err := v.VMPidFilePath.Read()
+ if err != nil {
+ return err
+ }
+ vmPid, err := strconv.Atoi(strings.TrimSpace(string(vmPidString)))
+ if err != nil {
+ return err
+ }
+
+ fmt.Println("Waiting for VM to exit...")
+ for isProcessAlive(vmPid) {
+ time.Sleep(500 * time.Millisecond)
}
- return v.ReadySocket.Delete()
+ return nil
}
// NewQMPMonitor creates the monitor subsection of our vm
@@ -880,8 +926,11 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
// remove socket and pid file if any: warn at low priority if things fail
// Remove the pidfile
+ if err := v.VMPidFilePath.Delete(); err != nil {
+ logrus.Debugf("Error while removing VM pidfile: %v", err)
+ }
if err := v.PidFilePath.Delete(); err != nil {
- logrus.Debugf("Error while removing pidfile: %v", err)
+ logrus.Debugf("Error while removing proxy pidfile: %v", err)
}
// Remove socket
if err := v.QMPMonitor.Address.Delete(); err != nil {
@@ -914,7 +963,12 @@ func (v *MachineVM) State(bypass bool) (machine.Status, error) {
}
monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
if err != nil {
- // FIXME: this error should probably be returned
+ // If an improper cleanup was done and the socketmonitor was not deleted,
+ // it can appear as though the machine state is not stopped. Check for ECONNREFUSED
+ // almost assures us that the vm is stopped.
+ if errors.Is(err, syscall.ECONNREFUSED) {
+ return machine.Stopped, nil
+ }
return "", err
}
if err := monitor.Connect(); err != nil {
@@ -1058,6 +1112,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
listEntry.RemoteUsername = vm.RemoteUsername
listEntry.IdentityPath = vm.IdentityPath
listEntry.CreatedAt = vm.Created
+ listEntry.Starting = vm.Starting
if listEntry.CreatedAt.IsZero() {
listEntry.CreatedAt = time.Now()
@@ -1071,6 +1126,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
if err != nil {
return err
}
+ listEntry.Running = state == machine.Running
if !vm.LastUp.IsZero() { // this means we have already written a time to the config
listEntry.LastUp = vm.LastUp
@@ -1081,12 +1137,6 @@ func getVMInfos() ([]*machine.ListResponse, error) {
return err
}
}
- switch state {
- case machine.Running:
- listEntry.Running = true
- case machine.Starting:
- listEntry.Starting = true
- }
listed = append(listed, listEntry)
}
@@ -1126,7 +1176,7 @@ func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
}
// startHostNetworking runs a binary on the host system that allows users
-// to setup port forwarding to the podman virtual machine
+// to set up port forwarding to the podman virtual machine
func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
cfg, err := config.Default()
if err != nil {
@@ -1293,13 +1343,19 @@ func (v *MachineVM) setPIDSocket() error {
if !rootless.IsRootless() {
rtPath = "/run"
}
- pidFileName := fmt.Sprintf("%s.pid", v.Name)
socketDir := filepath.Join(rtPath, "podman")
- pidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName)
+ vmPidFileName := fmt.Sprintf("%s_vm.pid", v.Name)
+ proxyPidFileName := fmt.Sprintf("%s_proxy.pid", v.Name)
+ vmPidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, vmPidFileName), &vmPidFileName)
+ if err != nil {
+ return err
+ }
+ proxyPidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, proxyPidFileName), &proxyPidFileName)
if err != nil {
return err
}
- v.PidFilePath = *pidFilePath
+ v.VMPidFilePath = *vmPidFilePath
+ v.PidFilePath = *proxyPidFilePath
return nil
}
@@ -1636,3 +1692,12 @@ func (p *Provider) RemoveAndCleanMachines() error {
}
return prevErr
}
+
+func isProcessAlive(pid int) bool {
+ err := unix.Kill(pid, syscall.Signal(0))
+ if err == nil || err == unix.EPERM {
+ return true
+ }
+
+ return false
+}
diff --git a/pkg/machine/qemu/options_darwin_arm64.go b/pkg/machine/qemu/options_darwin_arm64.go
index 4c954af00..d75237938 100644
--- a/pkg/machine/qemu/options_darwin_arm64.go
+++ b/pkg/machine/qemu/options_darwin_arm64.go
@@ -4,6 +4,8 @@ import (
"os"
"os/exec"
"path/filepath"
+
+ "github.com/containers/common/pkg/config"
)
var (
@@ -15,8 +17,8 @@ func (v *MachineVM) addArchOptions() []string {
opts := []string{
"-accel", "hvf",
"-accel", "tcg",
- "-cpu", "cortex-a57",
- "-M", "virt,highmem=off",
+ "-cpu", "host",
+ "-M", "virt,highmem=on",
"-drive", "file=" + getEdk2CodeFd("edk2-aarch64-code.fd") + ",if=pflash,format=raw,readonly=on",
"-drive", "file=" + ovmfDir + ",if=pflash,format=raw"}
return opts
@@ -38,6 +40,22 @@ func getOvmfDir(imagePath, vmName string) string {
}
/*
+ * When QEmu is installed in a non-default location in the system
+ * we can use the qemu-system-* binary path to figure the install
+ * location for Qemu and use it to look for edk2-code-fd
+ */
+func getEdk2CodeFdPathFromQemuBinaryPath() string {
+ cfg, err := config.Default()
+ if err == nil {
+ execPath, err := cfg.FindHelperBinary(QemuCommand, true)
+ if err == nil {
+ return filepath.Clean(filepath.Join(filepath.Dir(execPath), "..", "share", "qemu"))
+ }
+ }
+ return ""
+}
+
+/*
* QEmu can be installed in multiple locations on MacOS, especially on
* Apple Silicon systems. A build from source will likely install it in
* /usr/local/bin, whereas Homebrew package management standard is to
@@ -45,6 +63,7 @@ func getOvmfDir(imagePath, vmName string) string {
*/
func getEdk2CodeFd(name string) string {
dirs := []string{
+ getEdk2CodeFdPathFromQemuBinaryPath(),
"/opt/homebrew/opt/podman/libexec/share/qemu",
"/usr/local/share/qemu",
"/opt/homebrew/share/qemu",