diff --git a/cmd/podman/machine/restart.go b/cmd/podman/machine/restart.go new file mode 100644 index 00000000000..365789056ea --- /dev/null +++ b/cmd/podman/machine/restart.go @@ -0,0 +1,67 @@ +//go:build amd64 || arm64 + +package machine + +import ( + "fmt" + + "github.com/spf13/cobra" + "go.podman.io/podman/v6/cmd/podman/registry" + "go.podman.io/podman/v6/libpod/events" + "go.podman.io/podman/v6/pkg/machine" + "go.podman.io/podman/v6/pkg/machine/shim" +) + +var ( + restartCmd = &cobra.Command{ + Use: "restart [options] [MACHINE]", + Short: "Restart an existing machine", + Long: "Restart a managed virtual machine", + PersistentPreRunE: machinePreRunE, + RunE: restart, + Args: cobra.MaximumNArgs(1), + Example: `podman machine restart podman-machine-default`, + ValidArgsFunction: AutocompleteMachine, + } + restartOpts = machine.StartOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: restartCmd, + Parent: machineCmd, + }) + + flags := restartCmd.Flags() + noInfoFlagName := "no-info" + flags.BoolVar(&restartOpts.NoInfo, noInfoFlagName, false, "Suppress informational tips") + + quietFlagName := "quiet" + flags.BoolVarP(&restartOpts.Quiet, quietFlagName, "q", false, "Suppress machine restarting status output") +} + +func restart(_ *cobra.Command, args []string) error { + restartOpts.NoInfo = restartOpts.Quiet || restartOpts.NoInfo + vmName := defaultMachineName + if len(args) > 0 && len(args[0]) > 0 { + vmName = args[0] + } + + mc, vmProvider, err := shim.VMExists(vmName) + if err != nil { + return err + } + + if !restartOpts.Quiet { + fmt.Printf("Restarting machine %q\n", vmName) + } + + updateConnection := false + if err := shim.StopThenStart(mc, vmProvider, false, restartOpts, &updateConnection); err != nil { + return err + } + fmt.Printf("Machine %q restarted successfully\n", vmName) + newMachineEvent(events.Stop, events.Event{Name: vmName}) + newMachineEvent(events.Start, events.Event{Name: vmName}) + return nil +} diff --git a/docs/source/markdown/podman-machine-restart.1.md b/docs/source/markdown/podman-machine-restart.1.md new file mode 100644 index 00000000000..3e91420250d --- /dev/null +++ b/docs/source/markdown/podman-machine-restart.1.md @@ -0,0 +1,46 @@ +% podman-machine-restart 1 + +## NAME +podman\-machine\-restart - Restart a virtual machine + +## SYNOPSIS +**podman machine restart** [*options*] [*name*] + +## DESCRIPTION + +Restarts a virtual machine for Podman. + +The default machine name is `podman-machine-default`. If a machine name is not specified as an argument, +then `podman-machine-default` will be restarted. + +Stopping an already stopped virtual machine is not considered an error so running restart on a stopped +virtual machine just starts it from a stopped state. + +**podman machine restart** stops and then starts a Linux virtual machine where containers are run. + +## OPTIONS + +#### **--help** + +Print usage statement. + +#### **--no-info** + +Suppress informational tips. + +#### **--quiet**, **-q** + +Suppress machine restarting status output. + +## EXAMPLES + +Restart a podman machine named myvm. +``` +$ podman machine restart myvm +``` + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)** + +## HISTORY +May 2026, Originally compiled by Jait Jacob diff --git a/docs/source/markdown/podman-machine.1.md b/docs/source/markdown/podman-machine.1.md index 1449d71b8b7..f2e59d16c93 100644 --- a/docs/source/markdown/podman-machine.1.md +++ b/docs/source/markdown/podman-machine.1.md @@ -44,6 +44,7 @@ supported by platform. The asterisk denotes the default provider for the platfo | list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines | | os | [podman-machine-os(1)](podman-machine-os.1.md) | Manage a Podman virtual machine's OS | | reset | [podman-machine-reset(1)](podman-machine-reset.1.md) | Reset Podman machines and environment | +| restart | [podman-machine-restart(1)](podman-machine-restart.1.md) | Restart a virtual machine | | rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine | | set | [podman-machine-set(1)](podman-machine-set.1.md) | Set a virtual machine setting | | ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | @@ -51,7 +52,7 @@ supported by platform. The asterisk denotes the default provider for the platfo | stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-machine-cp(1)](podman-machine-cp.1.md)**, **[podman-machine-info(1)](podman-machine-info.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-os(1)](podman-machine-os.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**, **[podman-machine-inspect(1)](podman-machine-inspect.1.md)**, **[podman-machine-reset(1)](podman-machine-reset.1.md)**, **[containers.conf(5)](https://github.com/containers/container-libs/blob/main/common/docs/containers.conf.5.md)** +**[podman(1)](podman.1.md)**, **[podman-machine-cp(1)](podman-machine-cp.1.md)**, **[podman-machine-info(1)](podman-machine-info.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-os(1)](podman-machine-os.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**, **[podman-machine-inspect(1)](podman-machine-inspect.1.md)**, **[podman-machine-reset(1)](podman-machine-reset.1.md)**, **[podman-machine-restart(1)](podman-machine-restart.1.md)**, **[containers.conf(5)](https://github.com/containers/container-libs/blob/main/common/docs/containers.conf.5.md)** ### Troubleshooting diff --git a/pkg/machine/e2e/config_restart_test.go b/pkg/machine/e2e/config_restart_test.go new file mode 100644 index 00000000000..11bc28f5352 --- /dev/null +++ b/pkg/machine/e2e/config_restart_test.go @@ -0,0 +1,11 @@ +package e2e_test + +type restartMachine struct{} + +func (r restartMachine) buildCmd(m *machineTestBuilder) []string { + cmd := []string{"machine", "restart"} + if len(m.name) > 0 { + cmd = append(cmd, m.name) + } + return cmd +} diff --git a/pkg/machine/e2e/restart_test.go b/pkg/machine/e2e/restart_test.go new file mode 100644 index 00000000000..6dce1b40604 --- /dev/null +++ b/pkg/machine/e2e/restart_test.go @@ -0,0 +1,66 @@ +package e2e_test + +import ( + "fmt" + + "go.podman.io/podman/v6/pkg/machine/define" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("podman machine restart", func() { + It("should tell you when trying to restart a machine that doesn't exist", func() { + r := restartMachine{} + name := "aVMThatDoesntExist" + session, err := mb.setName(name).setCmd(r).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(125)) + Expect(session.errorToString()).To(ContainSubstring("VM does not exist")) + }) + + It("should restart a running machine", func() { + name := randomString() + i := new(initMachine) + session, err := mb.setName(name).setCmd(i.withImage(mb.imagePath).withNow().withVolume("")).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + + r := restartMachine{} + restartSession, err := mb.setName(name).setCmd(r).run() + Expect(err).ToNot(HaveOccurred()) + Expect(restartSession).To(Exit(0)) + Expect(restartSession.outputToString()).To(ContainSubstring(fmt.Sprintf("Machine %q restarted successfully", name))) + + inspect := new(inspectMachine) + inspectSession, err := mb.setName(name).setCmd(inspect.withFormat("{{.State}}")).run() + Expect(err).ToNot(HaveOccurred()) + Expect(inspectSession).To(Exit(0)) + Expect(inspectSession.outputToString()).To(Equal(define.Running)) + }) + + It("should start a stopped machine", func() { + name := randomString() + i := new(initMachine) + session, err := mb.setName(name).setCmd(i.withImage(mb.imagePath).withVolume("")).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + + inspect := new(inspectMachine) + inspectSession, err := mb.setName(name).setCmd(inspect.withFormat("{{.State}}")).run() + Expect(err).ToNot(HaveOccurred()) + Expect(inspectSession).To(Exit(0)) + Expect(inspectSession.outputToString()).To(Equal(define.Stopped)) + + r := restartMachine{} + restartSession, err := mb.setName(name).setCmd(r).run() + Expect(err).ToNot(HaveOccurred()) + Expect(restartSession).To(Exit(0)) + + inspectSession, err = mb.setName(name).setCmd(inspect.withFormat("{{.State}}")).run() + Expect(err).ToNot(HaveOccurred()) + Expect(inspectSession).To(Exit(0)) + Expect(inspectSession.outputToString()).To(Equal(define.Running)) + }) +}) diff --git a/pkg/machine/shim/host.go b/pkg/machine/shim/host.go index 8e09f64a752..4bccd2d30dc 100644 --- a/pkg/machine/shim/host.go +++ b/pkg/machine/shim/host.go @@ -418,6 +418,24 @@ func Stop(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, hardStop bool) e return stopLocked(mc, mp, dirs, hardStop) } +// StopThenStart stops and starts the machine while holding its lock. +func StopThenStart(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, hardStop bool, opts machine.StartOptions, updateSystemConn *bool) error { + dirs, err := env.GetMachineDirs(mp.VMType()) + if err != nil { + return err + } + mc.Lock() + defer mc.Unlock() + if err := mc.Refresh(); err != nil { + return fmt.Errorf("reload config: %w", err) + } + + if err := stopLocked(mc, mp, dirs, hardStop); err != nil { + return err + } + return startLocked(mc, mp, dirs, opts, updateSystemConn) +} + // stopLocked stops the machine and expects the caller to hold the machine's lock. func stopLocked(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDefine.MachineDirs, hardStop bool) error { state, err := mp.State(mc, false) @@ -463,12 +481,6 @@ func stopLocked(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *mach } func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, opts machine.StartOptions, updateSystemConn *bool) error { - var updateDefaultConnection bool - - defaultBackoff := 500 * time.Millisecond - maxBackoffs := 6 - signalChanClosed := false - dirs, err := env.GetMachineDirs(mp.VMType()) if err != nil { return err @@ -481,6 +493,17 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, opts machine.St return fmt.Errorf("reload config: %w", err) } + return startLocked(mc, mp, dirs, opts, updateSystemConn) +} + +// startLocked starts the machine and expects the caller to hold the machine's lock. +func startLocked(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDefine.MachineDirs, opts machine.StartOptions, updateSystemConn *bool) error { + var updateDefaultConnection bool + + defaultBackoff := 500 * time.Millisecond + maxBackoffs := 6 + signalChanClosed := false + connName := mc.Name if mc.HostUser.Rootful { connName += "-root"