summaryrefslogtreecommitdiff
path: root/pkg/machine
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/machine')
-rw-r--r--pkg/machine/config.go9
-rw-r--r--pkg/machine/config_test.go71
-rw-r--r--pkg/machine/fedora.go5
-rw-r--r--pkg/machine/ignition.go5
-rw-r--r--pkg/machine/pull.go2
-rw-r--r--pkg/machine/qemu/config.go124
-rw-r--r--pkg/machine/qemu/config_test.go103
-rw-r--r--pkg/machine/qemu/machine.go81
-rw-r--r--pkg/machine/wsl/machine.go7
9 files changed, 367 insertions, 40 deletions
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index b3b105150..7e1561506 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -29,16 +29,16 @@ type InitOptions struct {
Username string
ReExec bool
Rootful bool
- // The numberical userid of the user that called machine
+ // The numerical userid of the user that called machine
UID string
}
type QemuMachineStatus = string
const (
- // Running indicates the qemu vm is running
+ // Running indicates the qemu vm is running.
Running QemuMachineStatus = "running"
- // Stopped indicates the vm has stopped
+ // Stopped indicates the vm has stopped.
Stopped QemuMachineStatus = "stopped"
DefaultMachineName string = "podman-machine-default"
)
@@ -128,6 +128,7 @@ type DistributionDownload interface {
}
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
+ //TODO Should this function have input verification?
userInfo := url.User(userName)
uri := url.URL{
Scheme: "ssh",
@@ -147,7 +148,7 @@ func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url
}
// GetDataDir returns the filepath where vm images should
-// live for podman-machine
+// live for podman-machine.
func GetDataDir(vmType string) (string, error) {
data, err := homedir.GetDataHome()
if err != nil {
diff --git a/pkg/machine/config_test.go b/pkg/machine/config_test.go
new file mode 100644
index 000000000..d9fc5425e
--- /dev/null
+++ b/pkg/machine/config_test.go
@@ -0,0 +1,71 @@
+package machine
+
+import (
+ "net"
+ "net/url"
+ "reflect"
+ "testing"
+)
+
+func TestRemoteConnectionType_MakeSSHURL(t *testing.T) {
+ var (
+ host = "foobar"
+ path = "/path/to/socket"
+ rc = "ssh"
+ username = "core"
+ )
+ type args struct {
+ host string
+ path string
+ port string
+ userName string
+ }
+ tests := []struct {
+ name string
+ rc RemoteConnectionType
+ args args
+ want url.URL
+ }{
+ {
+ name: "Good no port",
+ rc: "ssh",
+ args: args{
+ host: host,
+ path: path,
+ port: "",
+ userName: username,
+ },
+ want: url.URL{
+ Scheme: rc,
+ User: url.User(username),
+ Host: host,
+ Path: path,
+ ForceQuery: false,
+ },
+ },
+ {
+ name: "Good with port",
+ rc: "ssh",
+ args: args{
+ host: host,
+ path: path,
+ port: "222",
+ userName: username,
+ },
+ want: url.URL{
+ Scheme: rc,
+ User: url.User(username),
+ Host: net.JoinHostPort(host, "222"),
+ Path: path,
+ ForceQuery: false,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := tt.rc.MakeSSHURL(tt.args.host, tt.args.path, tt.args.port, tt.args.userName); !reflect.DeepEqual(got, tt.want) { //nolint: scopelint
+ t.Errorf("MakeSSHURL() = %v, want %v", got, tt.want) //nolint: scopelint
+ }
+ })
+ }
+}
diff --git a/pkg/machine/fedora.go b/pkg/machine/fedora.go
index b26921b52..bed45c6da 100644
--- a/pkg/machine/fedora.go
+++ b/pkg/machine/fedora.go
@@ -59,7 +59,10 @@ func (f FedoraDownload) Get() *Download {
func (f FedoraDownload) HasUsableCache() (bool, error) {
info, err := os.Stat(f.LocalPath)
if err != nil {
- return false, nil
+ if errors.Is(err, os.ErrNotExist) {
+ return false, nil
+ }
+ return false, err
}
return info.Size() == f.Size, nil
}
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index b2dabb689..fe47437e3 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -6,6 +6,7 @@ package machine
import (
"encoding/json"
"fmt"
+ "io/fs"
"io/ioutil"
"net/url"
"os"
@@ -507,8 +508,8 @@ func getCerts(certsDir string, isDir bool) []File {
)
if isDir {
- err := filepath.Walk(certsDir, func(path string, info os.FileInfo, err error) error {
- if err == nil && !info.IsDir() {
+ err := filepath.WalkDir(certsDir, func(path string, d fs.DirEntry, err error) error {
+ if err == nil && !d.IsDir() {
certPath, err := filepath.Rel(certsDir, path)
if err != nil {
logrus.Warnf("%s", err)
diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go
index 26abedfcd..7e6f01bad 100644
--- a/pkg/machine/pull.go
+++ b/pkg/machine/pull.go
@@ -129,7 +129,7 @@ func DownloadVMImage(downloadURL *url2.URL, localImagePath string) error {
}()
if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("error downloading VM image %s: %s", downloadURL, resp.Status)
+ return fmt.Errorf("downloading VM image %s: %s", downloadURL, resp.Status)
}
size := resp.ContentLength
urlSplit := strings.Split(downloadURL.Path, "/")
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index b39334be0..408b33a33 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -4,12 +4,28 @@
package qemu
import (
+ "errors"
+ "os"
"time"
+
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // FCOS streams
+ // Testing FCOS stream
+ Testing string = "testing"
+ // Next FCOS stream
+ Next string = "next"
+ // Stable FCOS stream
+ Stable string = "stable"
)
type Provider struct{}
-type MachineVM struct {
+// Deprecated: MachineVMV1 is being deprecated in favor a more flexible and informative
+// structure
+type MachineVMV1 struct {
// CPUs to be assigned to the VM
CPUs uint64
// The command line representation of the qemu command
@@ -42,6 +58,74 @@ type MachineVM struct {
UID int
}
+type MachineVM struct {
+ // The command line representation of the qemu command
+ CmdLine []string
+ // HostUser contains info about host user
+ HostUser
+ // ImageConfig describes the bootable image
+ ImageConfig
+ // Mounts is the list of remote filesystems to mount
+ Mounts []Mount
+ // Name of VM
+ Name string
+ // PidFilePath is the where the PID file lives
+ PidFilePath MachineFile
+ // QMPMonitor is the qemu monitor object for sending commands
+ QMPMonitor Monitor
+ // ReadySocket tells host when vm is booted
+ ReadySocket MachineFile
+ // ResourceConfig is physical attrs of the VM
+ ResourceConfig
+ // SSHConfig for accessing the remote vm
+ SSHConfig
+}
+
+// ImageConfig describes the bootable image for the VM
+type ImageConfig struct {
+ IgnitionFilePath string
+ // ImageStream is the update stream for the image
+ ImageStream string
+ // ImagePath is the fq path to
+ ImagePath string
+}
+
+// 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
@@ -52,7 +136,7 @@ type Mount struct {
type Monitor struct {
// Address portion of the qmp monitor (/tmp/tmp.sock)
- Address string
+ Address MachineFile
// Network portion of the qmp monitor (unix)
Network string
// Timeout in seconds for qmp monitor transactions
@@ -61,6 +145,40 @@ type Monitor struct {
var (
// defaultQMPTimeout is the timeout duration for the
- // qmp monitor interactions
+ // qmp monitor interactions.
defaultQMPTimeout time.Duration = 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 {
+ logrus.Errorf("unable to remove symlink %q", *m.Symlink)
+ }
+ }
+ return os.Remove(m.Path)
+}
+
+// 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")
+ }
+ return &MachineFile{
+ Path: path,
+ Symlink: symlink,
+ }, nil
+}
diff --git a/pkg/machine/qemu/config_test.go b/pkg/machine/qemu/config_test.go
new file mode 100644
index 000000000..e3e7437b5
--- /dev/null
+++ b/pkg/machine/qemu/config_test.go
@@ -0,0 +1,103 @@
+package qemu
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestMachineFile_GetPath(t *testing.T) {
+ path := "/var/tmp/podman/my.sock"
+ sym := "/tmp/podman/my.sock"
+ type fields struct {
+ Path string
+ Symlink *string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want string
+ }{
+ {
+ name: "Original path",
+ fields: fields{path, nil},
+ want: path,
+ },
+ {
+ name: "Symlink over path",
+ fields: fields{
+ Path: path,
+ Symlink: &sym,
+ },
+ want: sym,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ m := &MachineFile{
+ Path: tt.fields.Path, //nolint: scopelint
+ Symlink: tt.fields.Symlink, //nolint: scopelint
+ }
+ if got := m.GetPath(); got != tt.want { //nolint: scopelint
+ t.Errorf("GetPath() = %v, want %v", got, tt.want) //nolint: scopelint
+ }
+ })
+ }
+}
+
+func TestNewMachineFile(t *testing.T) {
+ p := "/var/tmp/podman/my.sock"
+ sym := "/tmp/podman/my.sock"
+ empty := ""
+
+ m := MachineFile{
+ Path: p,
+ Symlink: nil,
+ }
+ type args struct {
+ path string
+ symlink *string
+ }
+ tests := []struct {
+ name string
+ args args
+ want *MachineFile
+ wantErr bool
+ }{
+ {
+ name: "Good",
+ args: args{path: p},
+ want: &m,
+ wantErr: false,
+ },
+ {
+ name: "Good with Symlink",
+ args: args{p, &sym},
+ want: &MachineFile{p, &sym},
+ wantErr: false,
+ },
+ {
+ name: "Bad path name",
+ args: args{empty, nil},
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "Bad symlink name",
+ args: args{p, &empty},
+ want: nil,
+ wantErr: true,
+ },
+ }
+ 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
+ t.Errorf("NewMachineFile() error = %v, wantErr %v", err, tt.wantErr) //nolint: scopelint
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) { //nolint: scopelint
+ t.Errorf("NewMachineFile() got = %v, want %v", got, tt.want) //nolint: scopelint
+ }
+ })
+ }
+}
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index d30e51215..ac8e7d75c 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -34,7 +34,7 @@ import (
var (
qemuProvider = &Provider{}
- // vmtype refers to qemu (vs libvirt, krun, etc)
+ // vmtype refers to qemu (vs libvirt, krun, etc).
vmtype = "qemu"
)
@@ -98,7 +98,7 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
return nil, err
}
- cmd := append([]string{execPath})
+ cmd := []string{execPath}
// Add memory
cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...)
// Add cpus
@@ -111,7 +111,7 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
return nil, err
}
vm.QMPMonitor = monitor
- cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server=on,wait=off"}...)
+ cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address.GetPath() + ",server=on,wait=off"}...)
// Add network
// Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is
@@ -134,7 +134,8 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
// LoadByName reads a json file that describes a known qemu vm
// and returns a vm instance
func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
- vm := &MachineVM{UID: -1} // posix reserves -1, so use it to signify undefined
+ vm := &MachineVM{Name: name}
+ vm.HostUser = HostUser{UID: -1} // posix reserves -1, so use it to signify undefined
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
@@ -176,7 +177,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
v.Rootful = opts.Rootful
switch opts.ImagePath {
- case "testing", "next", "stable", "":
+ case Testing, Next, Stable, "":
// Get image as usual
v.ImageStream = opts.ImagePath
dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath)
@@ -278,7 +279,9 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
}
// Write the JSON file
- v.writeConfig()
+ if err := v.writeConfig(); err != nil {
+ return false, fmt.Errorf("writing JSON file: %w", err)
+ }
// User has provided ignition file so keygen
// will be skipped.
@@ -315,7 +318,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
resize.Stdout = os.Stdout
resize.Stderr = os.Stderr
if err := resize.Run(); err != nil {
- return false, errors.Errorf("error resizing image: %q", err)
+ return false, errors.Errorf("resizing image: %q", err)
}
}
// If the user provides an ignition file, we need to
@@ -370,7 +373,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
conn net.Conn
err error
qemuSocketConn net.Conn
- wait time.Duration = time.Millisecond * 500
+ wait = time.Millisecond * 500
)
if v.isIncompatible() {
@@ -428,13 +431,29 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
// Disable graphic window when not in debug mode
// Done in start, so we're not suck with the debug level we used on init
- if logrus.GetLevel() != logrus.DebugLevel {
+ if !logrus.IsLevelEnabled(logrus.DebugLevel) {
cmd = append(cmd, "-display", "none")
}
_, err = os.StartProcess(v.CmdLine[0], cmd, attr)
if err != nil {
- return err
+ // check if qemu was not found
+ if !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ // lookup qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394
+ cfg, err := config.Default()
+ if err != nil {
+ return err
+ }
+ cmd[0], err = cfg.FindHelperBinary(QemuCommand, true)
+ if err != nil {
+ return err
+ }
+ _, err = os.StartProcess(cmd[0], cmd, attr)
+ if err != nil {
+ return err
+ }
}
fmt.Println("Waiting for VM ...")
socketPath, err := getRuntimeDir()
@@ -558,12 +577,12 @@ func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.QemuMachine
func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
var disconnected bool
// check if the qmp socket is there. if not, qemu instance is gone
- if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
+ if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
// Right now it is NOT an error to stop a stopped machine
logrus.Debugf("QMP monitor socket %v does not exist", v.QMPMonitor.Address)
return nil
}
- qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout)
+ qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
if err != nil {
return err
}
@@ -626,7 +645,8 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
}
if err := qmpMonitor.Disconnect(); err != nil {
- return nil
+ // FIXME: this error should probably be returned
+ return nil // nolint: nilerr
}
disconnected = true
@@ -665,20 +685,25 @@ 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)
+ if err != nil {
+ return Monitor{}, err
+ }
monitor := Monitor{
Network: network,
- Address: filepath.Join(rtDir, "qmp_"+name+".sock"),
+ Address: *address,
Timeout: timeout,
}
return monitor, nil
}
+// Remove deletes all the files associated with a machine including ssh keys, the image itself
func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
var (
files []string
)
- // cannot remove a running vm
+ // cannot remove a running vm unless --force is used
running, err := v.isRunning()
if err != nil {
return "", nil, err
@@ -749,13 +774,14 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun
func (v *MachineVM) isRunning() (bool, error) {
// Check if qmp socket path exists
- if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
+ if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
return false, nil
}
// Check if we can dial it
- monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout)
+ monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
if err != nil {
- return false, nil
+ // FIXME: this error should probably be returned
+ return false, nil // nolint: nilerr
}
if err := monitor.Connect(); err != nil {
return false, err
@@ -778,7 +804,7 @@ func (v *MachineVM) isRunning() (bool, error) {
func (v *MachineVM) isListening() bool {
// Check if we can dial it
- conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "localhost", v.Port), 10*time.Millisecond)
+ conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", v.Port), 10*time.Millisecond)
if err != nil {
return false
}
@@ -875,10 +901,10 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
var listed []*machine.ListResponse
- if err = filepath.Walk(vmConfigDir, func(path string, info os.FileInfo, err error) error {
+ if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
vm := new(MachineVM)
- if strings.HasSuffix(info.Name(), ".json") {
- fullPath := filepath.Join(vmConfigDir, info.Name())
+ if strings.HasSuffix(d.Name(), ".json") {
+ fullPath := filepath.Join(vmConfigDir, d.Name())
b, err := ioutil.ReadFile(fullPath)
if err != nil {
return err
@@ -1058,7 +1084,7 @@ func (v *MachineVM) isIncompatible() bool {
func (v *MachineVM) getForwardSocketPath() (string, error) {
path, err := machine.GetDataDir(v.Name)
if err != nil {
- logrus.Errorf("Error resolving data dir: %s", err.Error())
+ logrus.Errorf("Resolving data dir: %s", err.Error())
return "", nil
}
return filepath.Join(path, "podman.sock"), nil
@@ -1097,10 +1123,13 @@ func waitAndPingAPI(sock string) {
Transport: &http.Transport{
DialContext: func(context.Context, string, string) (net.Conn, error) {
con, err := net.DialTimeout("unix", sock, apiUpTimeout)
- if err == nil {
- con.SetDeadline(time.Now().Add(apiUpTimeout))
+ if err != nil {
+ return nil, err
+ }
+ if err := con.SetDeadline(time.Now().Add(apiUpTimeout)); err != nil {
+ return nil, err
}
- return con, err
+ return con, nil
},
},
}
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
index 5b0c757f0..5128fa313 100644
--- a/pkg/machine/wsl/machine.go
+++ b/pkg/machine/wsl/machine.go
@@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "io/fs"
"io/ioutil"
"net/url"
"os"
@@ -1175,10 +1176,10 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
var listed []*machine.ListResponse
- if err = filepath.Walk(vmConfigDir, func(path string, info os.FileInfo, err error) error {
+ if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
vm := new(MachineVM)
- if strings.HasSuffix(info.Name(), ".json") {
- fullPath := filepath.Join(vmConfigDir, info.Name())
+ if strings.HasSuffix(d.Name(), ".json") {
+ fullPath := filepath.Join(vmConfigDir, d.Name())
b, err := ioutil.ReadFile(fullPath)
if err != nil {
return err