Introduce machine inspect

Allow users to inspect their podman virtual machines. This will be
helpful for debug and development alike, because more details about the
machine can be collected.

Signed-off-by: Brent Baude <bbaude@redhat.com>

[NO NEW TESTS NEEDED]

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2022-04-01 15:31:13 -05:00
parent db7cd88c67
commit 8710197e85
6 changed files with 174 additions and 44 deletions

View File

@ -0,0 +1,90 @@
//go:build amd64 || arm64
// +build amd64 arm64
package machine
import (
"encoding/json"
"os"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/machine"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
inspectCmd = &cobra.Command{
Use: "inspect [options] [MACHINE...]",
Short: "Inspect an existing machine",
Long: "Provide details on a managed virtual machine",
RunE: inspect,
Example: `podman machine inspect myvm`,
ValidArgsFunction: autocompleteMachine,
}
inspectFlag = inspectFlagType{}
)
type inspectFlagType struct {
format string
}
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: inspectCmd,
Parent: machineCmd,
})
flags := inspectCmd.Flags()
formatFlagName := "format"
flags.StringVar(&inspectFlag.format, formatFlagName, "", "Format volume output using JSON or a Go template")
_ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(machine.InspectInfo{}))
}
func inspect(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
if len(args) < 1 {
args = append(args, defaultMachineName)
}
vms := make([]machine.InspectInfo, 0, len(args))
provider := getSystemDefaultProvider()
for _, vmName := range args {
vm, err := provider.LoadVMByName(vmName)
if err != nil {
errs = append(errs, err)
continue
}
state, err := vm.State()
if err != nil {
errs = append(errs, err)
continue
}
ii := machine.InspectInfo{
State: state,
VM: vm,
}
vms = append(vms, ii)
}
if len(inspectFlag.format) > 0 {
// need jhonce to work his template magic
return define.ErrNotImplemented
}
if err := printJSON(vms); err != nil {
logrus.Error(err)
}
return errs.PrintErrors()
}
func printJSON(data []machine.InspectInfo) error {
enc := json.NewEncoder(os.Stdout)
// by default, json marshallers will force utf=8 from
// a string. this breaks healthchecks that use <,>, &&.
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
return enc.Encode(data)
}

View File

@ -0,0 +1,35 @@
% podman-machine-inspect(1)
## NAME
podman\-machine\-inspect - Inspect one or more virtual machines
## SYNOPSIS
**podman machine inspect** [*options] *name* ...
## DESCRIPTION
Inspect one or more virtual machines
Obtain greater detail about Podman virtual machines. More than one virtual machine can be
inspected at once.
## OPTIONS
#### **--format**
Print results with a Go template.
#### **--help**
Print usage statement.
## EXAMPLES
```
$ podman machine inspect podman-machine-default
```
## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**
## HISTORY
April 2022, Originally compiled by Brent Baude <bbaude@redhat.com>

View File

@ -11,18 +11,19 @@ podman\-machine - Manage Podman's virtual machine
## SUBCOMMANDS ## SUBCOMMANDS
| Command | Man Page | Description | | Command | Man Page | Description |
| ------- | ------------------------------------------------------- | --------------------------------- | |---------|------------------------------------------------------|-----------------------------------|
| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine | | init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine |
| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines | | inspect | [podman-machine-inspect(1)](podman-machine-inspect.1.md) | Inspect one or more virtual machines |
| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine | | list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines |
| set | [podman-machine-set(1)](podman-machine-set.1.md) | Sets a virtual machine setting | | rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine |
| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | | set | [podman-machine-set(1)](podman-machine-set.1.md) | Sets a virtual machine setting |
| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | | ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine |
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | | start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine |
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine |
## SEE ALSO ## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.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(1)](podman.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.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)**
## HISTORY ## HISTORY
March 2021, Originally compiled by Ashley Cui <acui@redhat.com> March 2021, Originally compiled by Ashley Cui <acui@redhat.com>

View File

@ -33,14 +33,14 @@ type InitOptions struct {
UID string UID string
} }
type QemuMachineStatus = string type Status = string
const ( const (
// Running indicates the qemu vm is running. // Running indicates the qemu vm is running.
Running QemuMachineStatus = "running" Running Status = "running"
// Stopped indicates the vm has stopped. // Stopped indicates the vm has stopped.
Stopped QemuMachineStatus = "stopped" Stopped Status = "stopped"
DefaultMachineName string = "podman-machine-default" DefaultMachineName string = "podman-machine-default"
) )
type Provider interface { type Provider interface {
@ -113,12 +113,15 @@ type RemoveOptions struct {
SaveIgnition bool SaveIgnition bool
} }
type InspectOptions struct{}
type VM interface { type VM interface {
Init(opts InitOptions) (bool, error) Init(opts InitOptions) (bool, error)
Remove(name string, opts RemoveOptions) (string, func() error, error) Remove(name string, opts RemoveOptions) (string, func() error, error)
Set(name string, opts SetOptions) error Set(name string, opts SetOptions) error
SSH(name string, opts SSHOptions) error SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error Start(name string, opts StartOptions) error
State() (Status, error)
Stop(name string, opts StopOptions) error Stop(name string, opts StopOptions) error
} }
@ -126,6 +129,10 @@ type DistributionDownload interface {
HasUsableCache() (bool, error) HasUsableCache() (bool, error)
Get() *Download Get() *Download
} }
type InspectInfo struct {
State Status
VM
}
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL { func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
//TODO Should this function have input verification? //TODO Should this function have input verification?

View File

@ -439,12 +439,12 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) error {
return nil return nil
} }
running, err := v.isRunning() state, err := v.State()
if err != nil { if err != nil {
return err return err
} }
if running { if state == machine.Running {
suffix := "" suffix := ""
if v.Name != machine.DefaultMachineName { if v.Name != machine.DefaultMachineName {
suffix = " " + v.Name suffix = " " + v.Name
@ -581,14 +581,14 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
} }
if len(v.Mounts) > 0 { if len(v.Mounts) > 0 {
running, err := v.isRunning() state, err := v.State()
if err != nil { if err != nil {
return err return err
} }
listening := v.isListening() listening := v.isListening()
for !running || !listening { for state != machine.Running || !listening {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
running, err = v.isRunning() state, err = v.State()
if err != nil { if err != nil {
return err return err
} }
@ -634,7 +634,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return nil return nil
} }
func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.QemuMachineStatus, error) { func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, error) {
// this is the format returned from the monitor // this is the format returned from the monitor
// {"return": {"status": "running", "singlestep": false, "running": true}} // {"return": {"status": "running", "singlestep": false, "running": true}}
@ -748,11 +748,11 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
disconnected = true disconnected = true
waitInternal := 250 * time.Millisecond waitInternal := 250 * time.Millisecond
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
running, err := v.isRunning() state, err := v.State()
if err != nil { if err != nil {
return err return err
} }
if !running { if state != machine.Running {
break break
} }
time.Sleep(waitInternal) time.Sleep(waitInternal)
@ -800,11 +800,11 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
) )
// cannot remove a running vm unless --force is used // cannot remove a running vm unless --force is used
running, err := v.isRunning() state, err := v.State()
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
if running && !opts.Force { if state == machine.Running && !opts.Force {
return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
} }
@ -858,10 +858,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
confirmationMessage += "\n" confirmationMessage += "\n"
return confirmationMessage, func() error { return confirmationMessage, func() error {
for _, f := range files { for _, f := range files {
if err := os.Remove(f); err != nil { if err := os.Remove(f); err != nil && !errors.Is(err, os.ErrNotExist) {
if errors.Is(err, os.ErrNotExist) {
continue
}
logrus.Error(err) logrus.Error(err)
} }
} }
@ -869,19 +866,19 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
}, nil }, nil
} }
func (v *MachineVM) isRunning() (bool, error) { func (v *MachineVM) State() (machine.Status, error) {
// Check if qmp socket path exists // Check if qmp socket path exists
if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) { if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
return false, nil return "", nil
} }
// Check if we can dial it // Check if we can dial it
monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout) monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
if err != nil { if err != nil {
// FIXME: this error should probably be returned // FIXME: this error should probably be returned
return false, nil // nolint: nilerr return "", err
} }
if err := monitor.Connect(); err != nil { if err := monitor.Connect(); err != nil {
return false, err return "", err
} }
defer func() { defer func() {
if err := monitor.Disconnect(); err != nil { if err := monitor.Disconnect(); err != nil {
@ -889,14 +886,7 @@ func (v *MachineVM) isRunning() (bool, error) {
} }
}() }()
// If there is a monitor, lets see if we can query state // If there is a monitor, lets see if we can query state
state, err := v.checkStatus(monitor) return v.checkStatus(monitor)
if err != nil {
return false, err
}
if state == machine.Running {
return true, nil
}
return false, nil
} }
func (v *MachineVM) isListening() bool { func (v *MachineVM) isListening() bool {
@ -912,11 +902,11 @@ func (v *MachineVM) isListening() bool {
// SSH opens an interactive SSH session to the vm specified. // SSH opens an interactive SSH session to the vm specified.
// Added ssh function to VM interface: pkg/machine/config/go : line 58 // Added ssh function to VM interface: pkg/machine/config/go : line 58
func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error {
running, err := v.isRunning() state, err := v.State()
if err != nil { if err != nil {
return err return err
} }
if !running { if state != machine.Running {
return errors.Errorf("vm %q is not running.", v.Name) return errors.Errorf("vm %q is not running.", v.Name)
} }
@ -1037,11 +1027,11 @@ func getVMInfos() ([]*machine.ListResponse, error) {
return err return err
} }
listEntry.LastUp = fi.ModTime() listEntry.LastUp = fi.ModTime()
running, err := vm.isRunning() state, err := vm.State()
if err != nil { if err != nil {
return err return err
} }
if running { if state == machine.Running {
listEntry.Running = true listEntry.Running = true
} }

View File

@ -18,6 +18,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/utils" "github.com/containers/podman/v4/utils"
"github.com/containers/storage/pkg/homedir" "github.com/containers/storage/pkg/homedir"
@ -1013,6 +1014,12 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
return nil return nil
} }
// TODO: We need to rename isRunning to State(); I do not have a
// windows system to test this on.
func (v *MachineVM) State() (machine.Status, error) {
return "", define.ErrNotImplemented
}
func stopWinProxy(v *MachineVM) error { func stopWinProxy(v *MachineVM) error {
pid, tid, tidFile, err := readWinProxyTid(v) pid, tid, tidFile, err := readWinProxyTid(v)
if err != nil { if err != nil {