From 2902d32c4989a6c391d713fab7eb567eaac9d4cb Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 26 Apr 2022 16:04:57 -0500 Subject: Refactor machine inspect I was asked to refactor machine inspect output to represent more common and basic information. machine inspect now has information that would be appropriate for different machines. [NO NEW TESTS NEEDED] Signed-off-by: Brent Baude --- pkg/machine/config.go | 134 ++++++++++++++++++++++++++++++++++++- pkg/machine/qemu/config.go | 142 ++++------------------------------------ pkg/machine/qemu/config_test.go | 15 +++-- pkg/machine/qemu/machine.go | 72 ++++++++++++-------- pkg/machine/wsl/machine.go | 6 +- 5 files changed, 201 insertions(+), 168 deletions(-) (limited to 'pkg/machine') diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 833f9cba8..9a0ce757a 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -4,6 +4,8 @@ package machine import ( + errors2 "errors" + "io/ioutil" "net" "net/url" "os" @@ -12,6 +14,7 @@ import ( "github.com/containers/storage/pkg/homedir" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) type InitOptions struct { @@ -68,7 +71,7 @@ type Download struct { Artifact string CompressionType string Format string - ImageName string `json:"image_name"` + ImageName string LocalPath string LocalUncompressedFile string Sha256sum string @@ -120,6 +123,7 @@ type InspectOptions struct{} type VM interface { Init(opts InitOptions) (bool, error) + Inspect() (*InspectInfo, error) Remove(name string, opts RemoveOptions) (string, func() error, error) Set(name string, opts SetOptions) ([]error, error) SSH(name string, opts SSHOptions) error @@ -133,8 +137,14 @@ type DistributionDownload interface { Get() *Download } type InspectInfo struct { - State Status - VM + ConfigPath VMFile + Created time.Time + Image ImageConfig + LastUp time.Time + Name string + Resources ResourceConfig + SSHConfig SSHConfig + State Status } func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL { @@ -186,3 +196,121 @@ func GetConfDir(vmType string) (string, error) { mkdirErr := os.MkdirAll(confDir, 0755) return confDir, mkdirErr } + +// ResourceConfig describes physical attributes of the machine +type ResourceConfig struct { + // CPUs to be assigned to the VM + CPUs uint64 + // Disk size in gigabytes assigned to the vm + DiskSize uint64 + // Memory in megabytes assigned to the vm + Memory uint64 +} + +const maxSocketPathLength int = 103 + +type VMFile struct { + // Path is the fully qualified path to a file + Path string + // Symlink is a shortened version of Path by using + // a symlink + Symlink *string `json:"symlink,omitempty"` +} + +// GetPath returns the working path for a machinefile. it returns +// the symlink unless one does not exist +func (m *VMFile) GetPath() string { + if m.Symlink == nil { + return m.Path + } + return *m.Symlink +} + +// Delete removes the machinefile symlink (if it exists) and +// the actual path +func (m *VMFile) Delete() error { + if m.Symlink != nil { + if err := os.Remove(*m.Symlink); err != nil && !errors2.Is(err, os.ErrNotExist) { + logrus.Errorf("unable to remove symlink %q", *m.Symlink) + } + } + if err := os.Remove(m.Path); err != nil && !errors2.Is(err, os.ErrNotExist) { + return err + } + return nil +} + +// Read the contents of a given file and return in []bytes +func (m *VMFile) Read() ([]byte, error) { + return ioutil.ReadFile(m.GetPath()) +} + +// NewMachineFile is a constructor for VMFile +func NewMachineFile(path string, symlink *string) (*VMFile, error) { + if len(path) < 1 { + return nil, errors2.New("invalid machine file path") + } + if symlink != nil && len(*symlink) < 1 { + return nil, errors2.New("invalid symlink path") + } + mf := VMFile{Path: path} + if symlink != nil && len(path) > maxSocketPathLength { + if err := mf.makeSymlink(symlink); err != nil && !errors2.Is(err, os.ErrExist) { + return nil, err + } + } + return &mf, nil +} + +// makeSymlink for macOS creates a symlink in $HOME/.podman/ +// for a machinefile like a socket +func (m *VMFile) makeSymlink(symlink *string) error { + homedir, err := os.UserHomeDir() + if err != nil { + return err + } + sl := filepath.Join(homedir, ".podman", *symlink) + // make the symlink dir and throw away if it already exists + if err := os.MkdirAll(filepath.Dir(sl), 0700); err != nil && !errors2.Is(err, os.ErrNotExist) { + return err + } + m.Symlink = &sl + return os.Symlink(m.Path, sl) +} + +type Mount struct { + ReadOnly bool + Source string + Tag string + Target string + Type string +} + +// ImageConfig describes the bootable image for the VM +type ImageConfig struct { + // IgnitionFile is the path to the filesystem where the + // ignition file was written (if needs one) + IgnitionFile VMFile `json:"IgnitionFilePath"` + // ImageStream is the update stream for the image + ImageStream string + // ImageFile is the fq path to + ImagePath VMFile `json:"ImagePath"` +} + +// HostUser describes the host user +type HostUser struct { + // Whether this machine should run in a rootful or rootless manner + Rootful bool + // UID is the numerical id of the user that called machine + UID int +} + +// SSHConfig contains remote access information for SSH +type SSHConfig struct { + // IdentityPath is the fq path to the ssh priv key + IdentityPath string + // SSH port for user networking + Port int + // RemoteUsername of the vm user + RemoteUsername string +} diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go index 9473eef6f..56c95e3b3 100644 --- a/pkg/machine/qemu/config.go +++ b/pkg/machine/qemu/config.go @@ -4,13 +4,9 @@ package qemu import ( - "errors" - "io/ioutil" - "os" - "path/filepath" "time" - "github.com/sirupsen/logrus" + "github.com/containers/podman/v4/pkg/machine" ) const ( @@ -23,7 +19,7 @@ const ( Stable string = "stable" // Max length of fully qualified socket path - maxSocketPathLength int = 103 + ) type Provider struct{} @@ -36,7 +32,7 @@ type MachineVMV1 struct { // The command line representation of the qemu command CmdLine []string // Mounts is the list of remote filesystems to mount - Mounts []Mount + Mounts []machine.Mount // IdentityPath is the fq path to the ssh priv key IdentityPath string // IgnitionFilePath is the fq path to the .ign file @@ -65,27 +61,27 @@ type MachineVMV1 struct { type MachineVM struct { // ConfigPath is the path to the configuration file - ConfigPath MachineFile + ConfigPath machine.VMFile // The command line representation of the qemu command CmdLine []string // HostUser contains info about host user - HostUser + machine.HostUser // ImageConfig describes the bootable image - ImageConfig + machine.ImageConfig // Mounts is the list of remote filesystems to mount - Mounts []Mount + Mounts []machine.Mount // Name of VM Name string // PidFilePath is the where the PID file lives - PidFilePath MachineFile + PidFilePath machine.VMFile // QMPMonitor is the qemu monitor object for sending commands QMPMonitor Monitor // ReadySocket tells host when vm is booted - ReadySocket MachineFile + ReadySocket machine.VMFile // ResourceConfig is physical attrs of the VM - ResourceConfig + machine.ResourceConfig // SSHConfig for accessing the remote vm - SSHConfig + machine.SSHConfig // Starting tells us whether the machine is running or if we have just dialed it to start it Starting bool // Created contains the original created time instead of querying the file mod time @@ -94,59 +90,6 @@ type MachineVM struct { LastUp time.Time } -// ImageConfig describes the bootable image for the VM -type ImageConfig struct { - IgnitionFilePath MachineFile - // ImageStream is the update stream for the image - ImageStream string - // ImagePath is the fq path to - ImagePath MachineFile -} - -// HostUser describes the host user -type HostUser struct { - // Whether this machine should run in a rootful or rootless manner - Rootful bool - // UID is the numerical id of the user that called machine - UID int -} - -// SSHConfig contains remote access information for SSH -type SSHConfig struct { - // IdentityPath is the fq path to the ssh priv key - IdentityPath string - // SSH port for user networking - Port int - // RemoteUsername of the vm user - RemoteUsername string -} - -// ResourceConfig describes physical attributes of the machine -type ResourceConfig struct { - // CPUs to be assigned to the VM - CPUs uint64 - // Memory in megabytes assigned to the vm - Memory uint64 - // Disk size in gigabytes assigned to the vm - DiskSize uint64 -} - -type MachineFile struct { - // Path is the fully qualified path to a file - Path string - // Symlink is a shortened version of Path by using - // a symlink - Symlink *string -} - -type Mount struct { - Type string - Tag string - Source string - Target string - ReadOnly bool -} - type Monitorv1 struct { // Address portion of the qmp monitor (/tmp/tmp.sock) Address string @@ -158,7 +101,7 @@ type Monitorv1 struct { type Monitor struct { // Address portion of the qmp monitor (/tmp/tmp.sock) - Address MachineFile + Address machine.VMFile // Network portion of the qmp monitor (unix) Network string // Timeout in seconds for qmp monitor transactions @@ -170,64 +113,3 @@ var ( // qmp monitor interactions. defaultQMPTimeout = 2 * time.Second ) - -// GetPath returns the working path for a machinefile. it returns -// the symlink unless one does not exist -func (m *MachineFile) GetPath() string { - if m.Symlink == nil { - return m.Path - } - return *m.Symlink -} - -// Delete removes the machinefile symlink (if it exists) and -// the actual path -func (m *MachineFile) Delete() error { - if m.Symlink != nil { - if err := os.Remove(*m.Symlink); err != nil && !errors.Is(err, os.ErrNotExist) { - logrus.Errorf("unable to remove symlink %q", *m.Symlink) - } - } - if err := os.Remove(m.Path); err != nil && !errors.Is(err, os.ErrNotExist) { - return err - } - return nil -} - -// Read the contents of a given file and return in []bytes -func (m *MachineFile) Read() ([]byte, error) { - return ioutil.ReadFile(m.GetPath()) -} - -// NewMachineFile is a constructor for MachineFile -func NewMachineFile(path string, symlink *string) (*MachineFile, error) { - if len(path) < 1 { - return nil, errors.New("invalid machine file path") - } - if symlink != nil && len(*symlink) < 1 { - return nil, errors.New("invalid symlink path") - } - mf := MachineFile{Path: path} - if symlink != nil && len(path) > maxSocketPathLength { - if err := mf.makeSymlink(symlink); err != nil && !errors.Is(err, os.ErrExist) { - return nil, err - } - } - return &mf, nil -} - -// makeSymlink for macOS creates a symlink in $HOME/.podman/ -// for a machinefile like a socket -func (m *MachineFile) makeSymlink(symlink *string) error { - homedir, err := os.UserHomeDir() - if err != nil { - return err - } - sl := filepath.Join(homedir, ".podman", *symlink) - // make the symlink dir and throw away if it already exists - if err := os.MkdirAll(filepath.Dir(sl), 0700); err != nil && !errors.Is(err, os.ErrNotExist) { - return err - } - m.Symlink = &sl - return os.Symlink(m.Path, sl) -} diff --git a/pkg/machine/qemu/config_test.go b/pkg/machine/qemu/config_test.go index 264de9ae8..0fbb5b3bf 100644 --- a/pkg/machine/qemu/config_test.go +++ b/pkg/machine/qemu/config_test.go @@ -6,6 +6,7 @@ import ( "reflect" "testing" + "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/test/utils" ) @@ -37,7 +38,7 @@ func TestMachineFile_GetPath(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - m := &MachineFile{ + m := &machine.VMFile{ Path: tt.fields.Path, //nolint: scopelint Symlink: tt.fields.Symlink, //nolint: scopelint } @@ -73,7 +74,7 @@ func TestNewMachineFile(t *testing.T) { sym := "my.sock" longSym := filepath.Join(homedir, ".podman", sym) - m := MachineFile{ + m := machine.VMFile{ Path: p, Symlink: nil, } @@ -84,7 +85,7 @@ func TestNewMachineFile(t *testing.T) { tests := []struct { name string args args - want *MachineFile + want *machine.VMFile wantErr bool }{ { @@ -96,7 +97,7 @@ func TestNewMachineFile(t *testing.T) { { name: "Good with short symlink", args: args{p, &sym}, - want: &MachineFile{p, nil}, + want: &machine.VMFile{Path: p}, wantErr: false, }, { @@ -114,14 +115,14 @@ func TestNewMachineFile(t *testing.T) { { name: "Good with long symlink", args: args{longp, &sym}, - want: &MachineFile{longp, &longSym}, + want: &machine.VMFile{Path: longp, Symlink: &longSym}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewMachineFile(tt.args.path, tt.args.symlink) //nolint: scopelint - if (err != nil) != tt.wantErr { //nolint: scopelint + got, err := machine.NewMachineFile(tt.args.path, tt.args.symlink) //nolint: scopelint + if (err != nil) != tt.wantErr { //nolint: scopelint t.Errorf("NewMachineFile() error = %v, wantErr %v", err, tt.wantErr) //nolint: scopelint return } diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index ccfad90f7..44f28f0c7 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -71,12 +71,12 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { if len(opts.Name) > 0 { vm.Name = opts.Name } - ignitionFile, err := NewMachineFile(filepath.Join(vmConfigDir, vm.Name+".ign"), nil) + ignitionFile, err := machine.NewMachineFile(filepath.Join(vmConfigDir, vm.Name+".ign"), nil) if err != nil { return nil, err } - vm.IgnitionFilePath = *ignitionFile - imagePath, err := NewMachineFile(opts.ImagePath, nil) + vm.IgnitionFile = *ignitionFile + imagePath, err := machine.NewMachineFile(opts.ImagePath, nil) if err != nil { return nil, err } @@ -105,14 +105,13 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { if err != nil { return nil, err } - cmd := []string{execPath} // Add memory cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...) // Add cpus cmd = append(cmd, []string{"-smp", strconv.Itoa(int(vm.CPUs))}...) // Add ignition file - cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFilePath.GetPath()}...) + cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFile.GetPath()}...) // Add qmp socket monitor, err := NewQMPMonitor("unix", vm.Name, defaultQMPTimeout) if err != nil { @@ -158,9 +157,9 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error { return err } - pidFilePath := MachineFile{Path: pidFile} + pidFilePath := machine.VMFile{Path: pidFile} qmpMonitor := Monitor{ - Address: MachineFile{Path: old.QMPMonitor.Address}, + Address: machine.VMFile{Path: old.QMPMonitor.Address}, Network: old.QMPMonitor.Network, Timeout: old.QMPMonitor.Timeout, } @@ -169,18 +168,18 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error { return err } virtualSocketPath := filepath.Join(socketPath, "podman", vm.Name+"_ready.sock") - readySocket := MachineFile{Path: virtualSocketPath} + readySocket := machine.VMFile{Path: virtualSocketPath} - vm.HostUser = HostUser{} - vm.ImageConfig = ImageConfig{} - vm.ResourceConfig = ResourceConfig{} - vm.SSHConfig = SSHConfig{} + vm.HostUser = machine.HostUser{} + vm.ImageConfig = machine.ImageConfig{} + vm.ResourceConfig = machine.ResourceConfig{} + vm.SSHConfig = machine.SSHConfig{} - ignitionFilePath, err := NewMachineFile(old.IgnitionFilePath, nil) + ignitionFilePath, err := machine.NewMachineFile(old.IgnitionFilePath, nil) if err != nil { return err } - imagePath, err := NewMachineFile(old.ImagePath, nil) + imagePath, err := machine.NewMachineFile(old.ImagePath, nil) if err != nil { return err } @@ -194,7 +193,7 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error { vm.CmdLine = old.CmdLine vm.DiskSize = old.DiskSize vm.IdentityPath = old.IdentityPath - vm.IgnitionFilePath = *ignitionFilePath + vm.IgnitionFile = *ignitionFilePath vm.ImagePath = *imagePath vm.ImageStream = old.ImageStream vm.Memory = old.Memory @@ -229,7 +228,7 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error { // and returns a vm instance func (p *Provider) LoadVMByName(name string) (machine.VM, error) { vm := &MachineVM{Name: name} - vm.HostUser = HostUser{UID: -1} // posix reserves -1, so use it to signify undefined + vm.HostUser = machine.HostUser{UID: -1} // posix reserves -1, so use it to signify undefined if err := vm.update(); err != nil { return nil, err } @@ -270,7 +269,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { if err != nil { return false, err } - uncompressedFile, err := NewMachineFile(dd.Get().LocalUncompressedFile, nil) + uncompressedFile, err := machine.NewMachineFile(dd.Get().LocalUncompressedFile, nil) if err != nil { return false, err } @@ -286,7 +285,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { if err != nil { return false, err } - imagePath, err := NewMachineFile(g.Get().LocalUncompressedFile, nil) + imagePath, err := machine.NewMachineFile(g.Get().LocalUncompressedFile, nil) if err != nil { return false, err } @@ -308,7 +307,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { return false, err } - mounts := []Mount{} + mounts := []machine.Mount{} for i, volume := range opts.Volumes { tag := fmt.Sprintf("vol%d", i) paths := strings.SplitN(volume, ":", 3) @@ -338,7 +337,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { virtfsOptions += ",readonly" } v.CmdLine = append(v.CmdLine, []string{"-virtfs", virtfsOptions}...) - mounts = append(mounts, Mount{Type: MountType9p, Tag: tag, Source: source, Target: target, ReadOnly: readonly}) + mounts = append(mounts, machine.Mount{Type: MountType9p, Tag: tag, Source: source, Target: target, ReadOnly: readonly}) } } v.Mounts = mounts @@ -809,7 +808,7 @@ func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) if timeout == 0 { timeout = defaultQMPTimeout } - address, err := NewMachineFile(filepath.Join(rtDir, "qmp_"+name+".sock"), nil) + address, err := machine.NewMachineFile(filepath.Join(rtDir, "qmp_"+name+".sock"), nil) if err != nil { return Monitor{}, err } @@ -1238,14 +1237,14 @@ func (v *MachineVM) userGlobalSocketLink() (string, error) { return filepath.Join(filepath.Dir(path), "podman.sock"), err } -func (v *MachineVM) forwardSocketPath() (*MachineFile, error) { +func (v *MachineVM) forwardSocketPath() (*machine.VMFile, error) { sockName := "podman.sock" path, err := machine.GetDataDir(v.Name) if err != nil { logrus.Errorf("Resolving data dir: %s", err.Error()) return nil, err } - return NewMachineFile(filepath.Join(path, sockName), &sockName) + return machine.NewMachineFile(filepath.Join(path, sockName), &sockName) } func (v *MachineVM) setConfigPath() error { @@ -1254,7 +1253,7 @@ func (v *MachineVM) setConfigPath() error { return err } - configPath, err := NewMachineFile(filepath.Join(vmConfigDir, v.Name)+".json", nil) + configPath, err := machine.NewMachineFile(filepath.Join(vmConfigDir, v.Name)+".json", nil) if err != nil { return err } @@ -1268,7 +1267,7 @@ func (v *MachineVM) setReadySocket() error { if err != nil { return err } - virtualSocketPath, err := NewMachineFile(filepath.Join(rtPath, "podman", readySocketName), &readySocketName) + virtualSocketPath, err := machine.NewMachineFile(filepath.Join(rtPath, "podman", readySocketName), &readySocketName) if err != nil { return err } @@ -1286,7 +1285,7 @@ func (v *MachineVM) setPIDSocket() error { } pidFileName := fmt.Sprintf("%s.pid", v.Name) socketDir := filepath.Join(rtPath, "podman") - pidFilePath, err := NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName) + pidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName) if err != nil { return err } @@ -1463,7 +1462,26 @@ func (v *MachineVM) getImageFile() string { // getIgnitionFile wrapper returns the path to the ignition file func (v *MachineVM) getIgnitionFile() string { - return v.IgnitionFilePath.GetPath() + return v.IgnitionFile.GetPath() +} + +// Inspect returns verbose detail about the machine +func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { + state, err := v.State(false) + if err != nil { + return nil, err + } + + return &machine.InspectInfo{ + ConfigPath: v.ConfigPath, + Created: v.Created, + Image: v.ImageConfig, + LastUp: v.LastUp, + Name: v.Name, + Resources: v.ResourceConfig, + SSHConfig: v.SSHConfig, + State: state, + }, nil } // resizeDisk increases the size of the machine's disk in GB. diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 1f1f2dcaf..addcb7c8a 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -155,7 +155,7 @@ type Provider struct{} type MachineVM struct { // IdentityPath is the fq path to the ssh priv key IdentityPath string - // IgnitionFilePath is the fq path to the .ign file + // ImageStream is the version of fcos being used ImageStream string // ImagePath is the fq path to ImagePath string @@ -1387,3 +1387,7 @@ func (v *MachineVM) setRootful(rootful bool) error { } return nil } + +func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { + return nil, define.ErrNotImplemented +} -- cgit v1.2.3-54-g00ecf