diff options
Diffstat (limited to 'pkg/machine/qemu/machine.go')
-rw-r--r-- | pkg/machine/qemu/machine.go | 152 |
1 files changed, 102 insertions, 50 deletions
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 288b2eeb0..ca7947e34 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -8,6 +8,7 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "io/fs" "io/ioutil" @@ -30,8 +31,8 @@ import ( "github.com/containers/storage/pkg/homedir" "github.com/digitalocean/go-qemu/qmp" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) var ( @@ -107,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))}...) @@ -135,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 } @@ -209,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 } @@ -432,12 +434,12 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { if v.Name != machine.DefaultMachineName { suffix = " " + v.Name } - return setErrors, errors.Errorf("cannot change settings while the vm is running, run 'podman machine stop%s' first", suffix) + return setErrors, fmt.Errorf("cannot change settings while the vm is running, run 'podman machine stop%s' first", suffix) } if opts.Rootful != nil && v.Rootful != *opts.Rootful { if err := v.setRootful(*opts.Rootful); err != nil { - setErrors = append(setErrors, errors.Wrapf(err, "failed to set rootful option")) + setErrors = append(setErrors, fmt.Errorf("failed to set rootful option: %w", err)) } else { v.Rootful = *opts.Rootful } @@ -455,7 +457,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { if opts.DiskSize != nil && v.DiskSize != *opts.DiskSize { if err := v.resizeDisk(*opts.DiskSize, v.DiskSize); err != nil { - setErrors = append(setErrors, errors.Wrapf(err, "failed to resize disk")) + setErrors = append(setErrors, fmt.Errorf("failed to resize disk: %w", err)) } else { v.DiskSize = *opts.DiskSize } @@ -512,7 +514,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { forwardSock, forwardState, err := v.startHostNetworking() if err != nil { - return errors.Errorf("unable to start host networking: %q", err) + return fmt.Errorf("unable to start host networking: %q", err) } rtPath, err := getRuntimeDir() @@ -580,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 @@ -591,7 +593,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { } _, err = os.StartProcess(cmd[0], cmd, attr) if err != nil { - return errors.Wrapf(err, "unable to execute %q", cmd) + return fmt.Errorf("unable to execute %q: %w", cmd, err) } } fmt.Println("Waiting for VM ...") @@ -698,7 +700,7 @@ func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, err } b, err := monitor.Run(input) if err != nil { - if errors.Cause(err) == os.ErrNotExist { + if errors.Is(err, os.ErrNotExist) { return machine.Stopped, nil } return "", err @@ -753,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 } @@ -772,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 @@ -788,22 +790,50 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error { // FIXME: this error should probably be returned 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 @@ -849,7 +879,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() } if state == machine.Running { if !opts.Force { - return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) + return "", nil, fmt.Errorf("running vm %q cannot be destroyed", v.Name) } err := v.Stop(v.Name, machine.StopOptions{}) if err != nil { @@ -896,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 { @@ -930,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 { @@ -963,7 +1001,7 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { return err } if state != machine.Running { - return errors.Errorf("vm %q is not running", v.Name) + return fmt.Errorf("vm %q is not running", v.Name) } username := opts.Username @@ -975,7 +1013,7 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { port := strconv.Itoa(v.Port) args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", - "-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR"} + "-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR", "-o", "SetEnv=LC_ALL="} if len(opts.Args) > 0 { args = append(args, opts.Args...) } else { @@ -1074,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() @@ -1087,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 @@ -1097,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) } @@ -1131,7 +1165,7 @@ func (p *Provider) IsValidVMName(name string) (bool, error) { func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) { vms, err := getVMInfos() if err != nil { - return false, "", errors.Wrap(err, "error checking VM active") + return false, "", fmt.Errorf("error checking VM active: %w", err) } for _, vm := range vms { if vm.Running || vm.Starting { @@ -1142,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 { @@ -1183,7 +1217,10 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { fmt.Println(cmd) } _, err = os.StartProcess(cmd[0], cmd, attr) - return forwardSock, state, errors.Wrapf(err, "unable to execute: %q", cmd) + if err != nil { + return "", 0, fmt.Errorf("unable to execute: %q: %w", cmd, err) + } + return forwardSock, state, nil } func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) { @@ -1309,13 +1346,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 } @@ -1446,7 +1489,7 @@ func (v *MachineVM) update() error { b, err := v.ConfigPath.Read() if err != nil { if errors.Is(err, os.ErrNotExist) { - return errors.Wrap(machine.ErrNoSuchVM, v.Name) + return fmt.Errorf("%v: %w", v.Name, machine.ErrNoSuchVM) } return err } @@ -1522,7 +1565,7 @@ func (v *MachineVM) resizeDisk(diskSize uint64, oldSize uint64) error { // only if the virtualdisk size is less than // the given disk size if diskSize < oldSize { - return errors.Errorf("new disk size must be larger than current disk size: %vGB", oldSize) + return fmt.Errorf("new disk size must be larger than current disk size: %vGB", oldSize) } // Find the qemu executable @@ -1538,7 +1581,7 @@ func (v *MachineVM) resizeDisk(diskSize uint64, oldSize uint64) error { resize.Stdout = os.Stdout resize.Stderr = os.Stderr if err := resize.Run(); err != nil { - return errors.Errorf("resizing image: %q", err) + return fmt.Errorf("resizing image: %q", err) } return nil @@ -1652,3 +1695,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 +} |