Implement machine inspect for WSL

Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
Jason T. Greene 2022-04-30 20:21:51 -05:00
parent 80315b9c86
commit 5b78f9576c
1 changed files with 126 additions and 75 deletions

View File

@ -18,7 +18,6 @@ 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"
@ -153,20 +152,22 @@ const (
type Provider struct{} type Provider struct{}
type MachineVM struct { type MachineVM struct {
// IdentityPath is the fq path to the ssh priv key // ConfigPath is the path to the configuration file
IdentityPath string ConfigPath string
// Created contains the original created time instead of querying the file mod time
Created time.Time
// ImageStream is the version of fcos being used // ImageStream is the version of fcos being used
ImageStream string ImageStream string
// ImagePath is the fq path to // ImagePath is the fq path to
ImagePath string ImagePath string
// LastUp contains the last recorded uptime
LastUp time.Time
// Name of the vm // Name of the vm
Name string Name string
// SSH port for user networking
Port int
// RemoteUsername of the vm user
RemoteUsername string
// Whether this machine should run in a rootful or rootless manner // Whether this machine should run in a rootful or rootless manner
Rootful bool Rootful bool
// SSH identity, username, etc
machine.SSHConfig
} }
type ExitCodeError struct { type ExitCodeError struct {
@ -181,16 +182,22 @@ func GetWSLProvider() machine.Provider {
return wslProvider return wslProvider
} }
// NewMachine initializes an instance of a virtual machine based on the qemu // NewMachine initializes an instance of a wsl machine
// virtualization.
func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vm := new(MachineVM) vm := new(MachineVM)
if len(opts.Name) > 0 { if len(opts.Name) > 0 {
vm.Name = opts.Name vm.Name = opts.Name
} }
configPath, err := getConfigPath(opts.Name)
if err != nil {
return vm, err
}
vm.ConfigPath = configPath
vm.ImagePath = opts.ImagePath vm.ImagePath = opts.ImagePath
vm.RemoteUsername = opts.Username vm.RemoteUsername = opts.Username
vm.Created = time.Now()
vm.LastUp = vm.Created
// Add a random port for ssh // Add a random port for ssh
port, err := utils.GetRandomPort() port, err := utils.GetRandomPort()
@ -202,25 +209,69 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
return vm, nil return vm, nil
} }
func getConfigPath(name string) (string, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return "", err
}
return filepath.Join(vmConfigDir, name+".json"), nil
}
// LoadByName reads a json file that describes a known qemu vm // LoadByName reads a json file that describes a known qemu vm
// and returns a vm instance // and returns a vm instance
func (p *Provider) LoadVMByName(name string) (machine.VM, error) { func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
configPath, err := getConfigPath(name)
if err != nil {
return nil, err
}
vm, err := readAndMigrate(configPath, name)
return vm, err
}
// readAndMigrate returns the content of the VM's
// configuration file in json
func readAndMigrate(configPath string, name string) (*MachineVM, error) {
vm := new(MachineVM) vm := new(MachineVM)
vmConfigDir, err := machine.GetConfDir(vmtype) b, err := os.ReadFile(configPath)
if err != nil { if err != nil {
return nil, err if errors.Is(err, os.ErrNotExist) {
} return nil, errors.Wrap(machine.ErrNoSuchVM, name)
b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json")) }
if os.IsNotExist(err) { return vm, err
return nil, errors.Wrap(machine.ErrNoSuchVM, name)
}
if err != nil {
return nil, err
} }
err = json.Unmarshal(b, vm) err = json.Unmarshal(b, vm)
if err == nil && vm.Created.IsZero() {
err = vm.migrate40(configPath)
}
return vm, err return vm, err
} }
func (v *MachineVM) migrate40(configPath string) error {
v.ConfigPath = configPath
fi, err := os.Stat(configPath)
if err != nil {
return err
}
v.Created = fi.ModTime()
v.LastUp = getLegacyLastStart(v)
return v.writeConfig()
}
func getLegacyLastStart(vm *MachineVM) time.Time {
vmDataDir, err := machine.GetDataDir(vmtype)
if err != nil {
return vm.Created
}
distDir := filepath.Join(vmDataDir, "wsldist")
start := filepath.Join(distDir, vm.Name, "laststart")
info, err := os.Stat(start)
if err != nil {
return vm.Created
}
return info.ModTime()
}
// Init writes the json configuration file to the filesystem for // Init writes the json configuration file to the filesystem for
// other verbs (start, stop) // other verbs (start, stop)
func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
@ -289,12 +340,7 @@ func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
} }
func (v *MachineVM) writeConfig() error { func (v *MachineVM) writeConfig() error {
vmConfigDir, err := machine.GetConfDir(vmtype) jsonFile := v.ConfigPath
if err != nil {
return err
}
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
b, err := json.MarshalIndent(v, "", " ") b, err := json.MarshalIndent(v, "", " ")
if err != nil { if err != nil {
@ -810,7 +856,8 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
} }
} }
return markStart(name) _, _, err = v.updateTimeStamps(true)
return err
} }
func launchWinProxy(v *MachineVM) (bool, string, error) { func launchWinProxy(v *MachineVM) (bool, string, error) {
@ -1005,6 +1052,8 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
return errors.Errorf("%q is not running", v.Name) return errors.Errorf("%q is not running", v.Name)
} }
_, _, _ = v.updateTimeStamps(true)
if err := stopWinProxy(v); err != nil { if err := stopWinProxy(v); err != nil {
fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error()) fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
} }
@ -1032,10 +1081,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(bypass bool) (machine.Status, error) { func (v *MachineVM) State(bypass bool) (machine.Status, error) {
return "", define.ErrNotImplemented if v.isRunning() {
return machine.Running, nil
}
return machine.Stopped, nil
} }
func stopWinProxy(v *MachineVM) error { func stopWinProxy(v *MachineVM) error {
@ -1210,14 +1261,9 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
var listed []*machine.ListResponse var listed []*machine.ListResponse
if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error { if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
vm := new(MachineVM)
if strings.HasSuffix(d.Name(), ".json") { if strings.HasSuffix(d.Name(), ".json") {
fullPath := filepath.Join(vmConfigDir, d.Name()) path := filepath.Join(vmConfigDir, d.Name())
b, err := ioutil.ReadFile(fullPath) vm, err := readAndMigrate(path, strings.TrimSuffix(d.Name(), ".json"))
if err != nil {
return err
}
err = json.Unmarshal(b, vm)
if err != nil { if err != nil {
return err return err
} }
@ -1229,15 +1275,13 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
listEntry.CPUs, _ = getCPUs(vm) listEntry.CPUs, _ = getCPUs(vm)
listEntry.Memory, _ = getMem(vm) listEntry.Memory, _ = getMem(vm)
listEntry.DiskSize = getDiskSize(vm) listEntry.DiskSize = getDiskSize(vm)
fi, err := os.Stat(fullPath) listEntry.RemoteUsername = vm.RemoteUsername
if err != nil { listEntry.Port = vm.Port
return err listEntry.IdentityPath = vm.IdentityPath
}
listEntry.CreatedAt = fi.ModTime() running := vm.isRunning()
listEntry.LastUp = getLastStart(vm, fi.ModTime()) listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running)
if vm.isRunning() { listEntry.Running = running
listEntry.Running = true
}
listed = append(listed, listEntry) listed = append(listed, listEntry)
} }
@ -1248,6 +1292,16 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
return listed, err return listed, err
} }
func (vm *MachineVM) updateTimeStamps(updateLast bool) (time.Time, time.Time, error) {
var err error
if updateLast {
vm.LastUp = time.Now()
err = vm.writeConfig()
}
return vm.Created, vm.LastUp, err
}
func getDiskSize(vm *MachineVM) uint64 { func getDiskSize(vm *MachineVM) uint64 {
vmDataDir, err := machine.GetDataDir(vmtype) vmDataDir, err := machine.GetDataDir(vmtype)
if err != nil { if err != nil {
@ -1262,36 +1316,6 @@ func getDiskSize(vm *MachineVM) uint64 {
return uint64(info.Size()) return uint64(info.Size())
} }
func markStart(name string) error {
vmDataDir, err := machine.GetDataDir(vmtype)
if err != nil {
return err
}
distDir := filepath.Join(vmDataDir, "wsldist")
start := filepath.Join(distDir, name, "laststart")
file, err := os.Create(start)
if err != nil {
return err
}
file.Close()
return nil
}
func getLastStart(vm *MachineVM, created time.Time) time.Time {
vmDataDir, err := machine.GetDataDir(vmtype)
if err != nil {
return created
}
distDir := filepath.Join(vmDataDir, "wsldist")
start := filepath.Join(distDir, vm.Name, "laststart")
info, err := os.Stat(start)
if err != nil {
return created
}
return info.ModTime()
}
func getCPUs(vm *MachineVM) (uint64, error) { func getCPUs(vm *MachineVM) (uint64, error) {
dist := toDist(vm.Name) dist := toDist(vm.Name)
if run, _ := isWSLRunning(dist); !run { if run, _ := isWSLRunning(dist); !run {
@ -1388,6 +1412,33 @@ func (v *MachineVM) setRootful(rootful bool) error {
return nil return nil
} }
// Inspect returns verbose detail about the machine
func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { func (v *MachineVM) Inspect() (*machine.InspectInfo, error) {
return nil, define.ErrNotImplemented state, err := v.State(false)
if err != nil {
return nil, err
}
created, lastUp, _ := v.updateTimeStamps(state == machine.Running)
return &machine.InspectInfo{
ConfigPath: machine.VMFile{Path: v.ConfigPath},
Created: created,
Image: machine.ImageConfig{
ImagePath: machine.VMFile{Path: v.ImagePath},
ImageStream: v.ImageStream,
},
LastUp: lastUp,
Name: v.Name,
Resources: v.getResources(),
SSHConfig: v.SSHConfig,
State: state,
}, nil
}
func (v *MachineVM) getResources() (resources machine.ResourceConfig) {
resources.CPUs, _ = getCPUs(v)
resources.Memory, _ = getMem(v)
resources.DiskSize = getDiskSize(v)
return
} }