aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/decoder.go10
-rw-r--r--pkg/api/handlers/libpod/secrets.go8
-rw-r--r--pkg/domain/infra/abi/containers.go6
-rw-r--r--pkg/domain/infra/abi/parse/parse.go5
-rw-r--r--pkg/machine/config.go141
-rw-r--r--pkg/machine/e2e/config.go16
-rw-r--r--pkg/machine/e2e/config_set.go43
-rw-r--r--pkg/machine/e2e/init_test.go6
-rw-r--r--pkg/machine/e2e/inspect_test.go9
-rw-r--r--pkg/machine/e2e/list_test.go27
-rw-r--r--pkg/machine/e2e/rm_test.go11
-rw-r--r--pkg/machine/e2e/set_test.go134
-rw-r--r--pkg/machine/e2e/ssh_test.go11
-rw-r--r--pkg/machine/e2e/start_test.go5
-rw-r--r--pkg/machine/e2e/stop_test.go9
-rw-r--r--pkg/machine/qemu/config.go142
-rw-r--r--pkg/machine/qemu/config_test.go34
-rw-r--r--pkg/machine/qemu/machine.go221
-rw-r--r--pkg/machine/qemu/machine_test.go17
-rw-r--r--pkg/machine/wsl/machine.go258
20 files changed, 769 insertions, 344 deletions
diff --git a/pkg/api/handlers/decoder.go b/pkg/api/handlers/decoder.go
index 5e8f12960..fbe03d97b 100644
--- a/pkg/api/handlers/decoder.go
+++ b/pkg/api/handlers/decoder.go
@@ -21,6 +21,7 @@ func NewAPIDecoder() *schema.Decoder {
d.RegisterConverter(map[string][]string{}, convertURLValuesString)
d.RegisterConverter(time.Time{}, convertTimeString)
d.RegisterConverter(define.ContainerStatus(0), convertContainerStatusString)
+ d.RegisterConverter(map[string]string{}, convertStringMap)
var Signal syscall.Signal
d.RegisterConverter(Signal, convertSignal)
@@ -48,6 +49,15 @@ func convertURLValuesString(query string) reflect.Value {
return reflect.ValueOf(f)
}
+func convertStringMap(query string) reflect.Value {
+ res := make(map[string]string)
+ err := json.Unmarshal([]byte(query), &res)
+ if err != nil {
+ logrus.Infof("convertStringMap: Failed to Unmarshal %s: %s", query, err.Error())
+ }
+ return reflect.ValueOf(res)
+}
+
func convertContainerStatusString(query string) reflect.Value {
result, err := define.StringToContainerStatus(query)
if err != nil {
diff --git a/pkg/api/handlers/libpod/secrets.go b/pkg/api/handlers/libpod/secrets.go
index 8708e630c..3ea2c2ea8 100644
--- a/pkg/api/handlers/libpod/secrets.go
+++ b/pkg/api/handlers/libpod/secrets.go
@@ -1,9 +1,7 @@
package libpod
import (
- "encoding/json"
"net/http"
- "reflect"
"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/pkg/api/handlers/utils"
@@ -20,12 +18,6 @@ func CreateSecret(w http.ResponseWriter, r *http.Request) {
decoder = r.Context().Value(api.DecoderKey).(*schema.Decoder)
)
- decoder.RegisterConverter(map[string]string{}, func(str string) reflect.Value {
- res := make(map[string]string)
- json.Unmarshal([]byte(str), &res)
- return reflect.ValueOf(res)
- })
-
query := struct {
Name string `schema:"name"`
Driver string `schema:"driver"`
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index b56c36015..89b09bb1d 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -641,7 +641,11 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
}
restoreOptions.CheckpointImageID = img.ID()
mountPoint, err := img.Mount(ctx, nil, "")
- defer img.Unmount(true)
+ defer func() {
+ if err := img.Unmount(true); err != nil {
+ logrus.Errorf("Failed to unmount image: %v", err)
+ }
+ }()
if err != nil {
return nil, err
}
diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go
index 2d1adab74..3bac2ef99 100644
--- a/pkg/domain/infra/abi/parse/parse.go
+++ b/pkg/domain/infra/abi/parse/parse.go
@@ -73,6 +73,11 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error)
finalVal = append(finalVal, o)
// set option "GID": "$gid"
volumeOptions["GID"] = splitO[1]
+ case "noquota":
+ logrus.Debugf("Removing noquota from options and adding WithVolumeDisableQuota")
+ libpodOptions = append(libpodOptions, libpod.WithVolumeDisableQuota())
+ // set option "NOQUOTA": "true"
+ volumeOptions["NOQUOTA"] = "true"
default:
finalVal = append(finalVal, o)
}
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index 5bbaf8c51..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
@@ -95,7 +98,10 @@ type ListResponse struct {
}
type SetOptions struct {
- Rootful bool
+ CPUs *uint64
+ DiskSize *uint64
+ Memory *uint64
+ Rootful *bool
}
type SSHOptions struct {
@@ -117,8 +123,9 @@ 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
+ Set(name string, opts SetOptions) ([]error, error)
SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error
State(bypass bool) (Status, error)
@@ -130,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 {
@@ -183,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/e2e/config.go b/pkg/machine/e2e/config.go
index 7d75ca6bc..c17b840d3 100644
--- a/pkg/machine/e2e/config.go
+++ b/pkg/machine/e2e/config.go
@@ -131,7 +131,7 @@ func (m *machineTestBuilder) setTimeout(timeout time.Duration) *machineTestBuild
func (mb *machineTestBuilder) toQemuInspectInfo() ([]qemuMachineInspectInfo, int, error) {
args := []string{"machine", "inspect"}
args = append(args, mb.names...)
- session, err := runWrapper(mb.podmanBinary, args, defaultTimeout)
+ session, err := runWrapper(mb.podmanBinary, args, defaultTimeout, true)
if err != nil {
return nil, -1, err
}
@@ -140,11 +140,15 @@ func (mb *machineTestBuilder) toQemuInspectInfo() ([]qemuMachineInspectInfo, int
return mii, session.ExitCode(), err
}
+func (m *machineTestBuilder) runWithoutWait() (*machineSession, error) {
+ return runWrapper(m.podmanBinary, m.cmd, m.timeout, false)
+}
+
func (m *machineTestBuilder) run() (*machineSession, error) {
- return runWrapper(m.podmanBinary, m.cmd, m.timeout)
+ return runWrapper(m.podmanBinary, m.cmd, m.timeout, true)
}
-func runWrapper(podmanBinary string, cmdArgs []string, timeout time.Duration) (*machineSession, error) {
+func runWrapper(podmanBinary string, cmdArgs []string, timeout time.Duration, wait bool) (*machineSession, error) {
if len(os.Getenv("DEBUG")) > 0 {
cmdArgs = append([]string{"--log-level=debug"}, cmdArgs...)
}
@@ -156,8 +160,10 @@ func runWrapper(podmanBinary string, cmdArgs []string, timeout time.Duration) (*
return nil, err
}
ms := machineSession{session}
- ms.waitWithTimeout(timeout)
- fmt.Println("output:", ms.outputToString())
+ if wait {
+ ms.waitWithTimeout(timeout)
+ fmt.Println("output:", ms.outputToString())
+ }
return &ms, nil
}
diff --git a/pkg/machine/e2e/config_set.go b/pkg/machine/e2e/config_set.go
new file mode 100644
index 000000000..b310ab1b9
--- /dev/null
+++ b/pkg/machine/e2e/config_set.go
@@ -0,0 +1,43 @@
+package e2e
+
+import (
+ "strconv"
+)
+
+type setMachine struct {
+ cpus *uint
+ diskSize *uint
+ memory *uint
+
+ cmd []string
+}
+
+func (i *setMachine) buildCmd(m *machineTestBuilder) []string {
+ cmd := []string{"machine", "set"}
+ if i.cpus != nil {
+ cmd = append(cmd, "--cpus", strconv.Itoa(int(*i.cpus)))
+ }
+ if i.diskSize != nil {
+ cmd = append(cmd, "--disk-size", strconv.Itoa(int(*i.diskSize)))
+ }
+ if i.memory != nil {
+ cmd = append(cmd, "--memory", strconv.Itoa(int(*i.memory)))
+ }
+ cmd = append(cmd, m.name)
+ i.cmd = cmd
+ return cmd
+}
+
+func (i *setMachine) withCPUs(num uint) *setMachine {
+ i.cpus = &num
+ return i
+}
+func (i *setMachine) withDiskSize(size uint) *setMachine {
+ i.diskSize = &size
+ return i
+}
+
+func (i *setMachine) withMemory(num uint) *setMachine {
+ i.memory = &num
+ return i
+}
diff --git a/pkg/machine/e2e/init_test.go b/pkg/machine/e2e/init_test.go
index 309d460a9..304122738 100644
--- a/pkg/machine/e2e/init_test.go
+++ b/pkg/machine/e2e/init_test.go
@@ -28,13 +28,13 @@ var _ = Describe("podman machine init", func() {
reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
session, err := mb.setName(reallyLongName).setCmd(&i).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(125))
+ Expect(session).To(Exit(125))
})
It("simple init", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).To(Exit(0))
inspectBefore, ec, err := mb.toQemuInspectInfo()
Expect(err).To(BeNil())
@@ -52,7 +52,7 @@ var _ = Describe("podman machine init", func() {
i := initMachine{}
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).To(Exit(0))
inspectBefore, ec, err := mb.toQemuInspectInfo()
Expect(ec).To(BeZero())
diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go
index 30d810b8f..e282dd21d 100644
--- a/pkg/machine/e2e/inspect_test.go
+++ b/pkg/machine/e2e/inspect_test.go
@@ -7,6 +7,7 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("podman machine stop", func() {
@@ -27,24 +28,24 @@ var _ = Describe("podman machine stop", func() {
reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
session, err := mb.setName(reallyLongName).setCmd(&i).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(125))
+ Expect(session).To(Exit(125))
})
It("inspect two machines", func() {
i := new(initMachine)
foo1, err := mb.setName("foo1").setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
- Expect(foo1.ExitCode()).To(Equal(0))
+ Expect(foo1).To(Exit(0))
ii := new(initMachine)
foo2, err := mb.setName("foo2").setCmd(ii.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
- Expect(foo2.ExitCode()).To(Equal(0))
+ Expect(foo2).To(Exit(0))
inspect := new(inspectMachine)
inspectSession, err := mb.setName("foo1").setCmd(inspect).run()
Expect(err).To(BeNil())
- Expect(inspectSession.ExitCode()).To(Equal(0))
+ Expect(inspectSession).To(Exit(0))
type fakeInfos struct {
Status string
diff --git a/pkg/machine/e2e/list_test.go b/pkg/machine/e2e/list_test.go
index e7a439945..0ce9063f9 100644
--- a/pkg/machine/e2e/list_test.go
+++ b/pkg/machine/e2e/list_test.go
@@ -70,6 +70,33 @@ var _ = Describe("podman machine list", func() {
Expect(util.StringInSlice(name1, listNames)).To(BeTrue())
Expect(util.StringInSlice(name2, listNames)).To(BeTrue())
})
+
+ It("list machine: check if running while starting", func() {
+ i := new(initMachine)
+ session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session).To(Exit(0))
+ s := new(startMachine)
+ startSession, err := mb.setCmd(s).runWithoutWait()
+ Expect(err).To(BeNil())
+ l := new(listMachine)
+ for { // needs to be infinite because we need to check if running when inspect returns to avoid race conditions.
+ listSession, err := mb.setCmd(l).run()
+ Expect(listSession).To(Exit(0))
+ Expect(err).To(BeNil())
+ if startSession.ExitCode() == -1 {
+ Expect(listSession.outputToString()).NotTo(ContainSubstring("Currently running"))
+ } else {
+ break
+ }
+ }
+ Expect(startSession).To(Exit(0))
+ listSession, err := mb.setCmd(l).run()
+ Expect(listSession).To(Exit(0))
+ Expect(err).To(BeNil())
+ Expect(listSession.outputToString()).To(ContainSubstring("Currently running"))
+ Expect(listSession.outputToString()).NotTo(ContainSubstring("Less than a second ago")) // check to make sure time created is accurate
+ })
})
func stripAsterisk(sl []string) {
diff --git a/pkg/machine/e2e/rm_test.go b/pkg/machine/e2e/rm_test.go
index 011da5dde..43b8c594c 100644
--- a/pkg/machine/e2e/rm_test.go
+++ b/pkg/machine/e2e/rm_test.go
@@ -3,6 +3,7 @@ package e2e
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("podman machine rm", func() {
@@ -23,14 +24,14 @@ var _ = Describe("podman machine rm", func() {
reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
session, err := mb.setName(reallyLongName).setCmd(&i).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(125))
+ Expect(session).To(Exit(125))
})
It("Remove machine", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).To(Exit(0))
rm := rmMachine{}
_, err = mb.setCmd(rm.withForce()).run()
Expect(err).To(BeNil())
@@ -46,18 +47,18 @@ var _ = Describe("podman machine rm", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath).withNow()).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).To(Exit(0))
rm := new(rmMachine)
// Removing a running machine should fail
stop, err := mb.setCmd(rm).run()
Expect(err).To(BeNil())
- Expect(stop.ExitCode()).To(Equal(125))
+ Expect(stop).To(Exit(125))
// Removing again with force
stopAgain, err := mb.setCmd(rm.withForce()).run()
Expect(err).To(BeNil())
- Expect(stopAgain.ExitCode()).To(BeZero())
+ Expect(stopAgain).To(Exit(0))
// Inspect to be dead sure
_, ec, err := mb.toQemuInspectInfo()
diff --git a/pkg/machine/e2e/set_test.go b/pkg/machine/e2e/set_test.go
new file mode 100644
index 000000000..9af29c560
--- /dev/null
+++ b/pkg/machine/e2e/set_test.go
@@ -0,0 +1,134 @@
+package e2e
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("podman machine set", func() {
+ var (
+ mb *machineTestBuilder
+ testDir string
+ )
+
+ BeforeEach(func() {
+ testDir, mb = setup()
+ })
+ AfterEach(func() {
+ teardown(originalHomeDir, testDir, mb)
+ })
+
+ It("set machine cpus", func() {
+ name := randomString(12)
+ i := new(initMachine)
+ session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session).To(Exit(0))
+
+ set := setMachine{}
+ setSession, err := mb.setName(name).setCmd(set.withCPUs(2)).run()
+ Expect(err).To(BeNil())
+ Expect(setSession).To(Exit(0))
+
+ s := new(startMachine)
+ startSession, err := mb.setCmd(s).run()
+ Expect(err).To(BeNil())
+ Expect(startSession).To(Exit(0))
+
+ ssh2 := sshMachine{}
+ sshSession2, err := mb.setName(name).setCmd(ssh2.withSSHComand([]string{"lscpu", "|", "grep", "\"CPU(s):\"", "|", "head", "-1"})).run()
+ Expect(err).To(BeNil())
+ Expect(sshSession2).To(Exit(0))
+ Expect(sshSession2.outputToString()).To(ContainSubstring("2"))
+
+ // Setting a running machine results in 125
+ runner, err := mb.setName(name).setCmd(set.withCPUs(4)).run()
+ Expect(err).To(BeNil())
+ Expect(runner).To(Exit(125))
+ })
+
+ It("increase machine disk size", func() {
+ name := randomString(12)
+ i := new(initMachine)
+ session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session).To(Exit(0))
+
+ set := setMachine{}
+ setSession, err := mb.setName(name).setCmd(set.withDiskSize(102)).run()
+ Expect(err).To(BeNil())
+ Expect(setSession).To(Exit(0))
+
+ // shrinking disk size iss verboten
+ shrink, err := mb.setName(name).setCmd(set.withDiskSize(5)).run()
+ Expect(err).To(BeNil())
+ Expect(shrink).To(Exit(125))
+
+ s := new(startMachine)
+ startSession, err := mb.setCmd(s).run()
+ Expect(err).To(BeNil())
+ Expect(startSession).To(Exit(0))
+
+ ssh2 := sshMachine{}
+ sshSession2, err := mb.setName(name).setCmd(ssh2.withSSHComand([]string{"sudo", "fdisk", "-l", "|", "grep", "Disk"})).run()
+ Expect(err).To(BeNil())
+ Expect(sshSession2).To(Exit(0))
+ Expect(sshSession2.outputToString()).To(ContainSubstring("102 GiB"))
+ })
+
+ It("set machine ram", func() {
+ name := randomString(12)
+ i := new(initMachine)
+ session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session).To(Exit(0))
+
+ set := setMachine{}
+ setSession, err := mb.setName(name).setCmd(set.withMemory(4000)).run()
+ Expect(err).To(BeNil())
+ Expect(setSession).To(Exit(0))
+
+ s := new(startMachine)
+ startSession, err := mb.setCmd(s).run()
+ Expect(err).To(BeNil())
+ Expect(startSession).To(Exit(0))
+
+ ssh2 := sshMachine{}
+ sshSession2, err := mb.setName(name).setCmd(ssh2.withSSHComand([]string{"cat", "/proc/meminfo", "|", "numfmt", "--field", "2", "--from-unit=Ki", "--to-unit=Mi", "|", "sed", "'s/ kB/M/g'", "|", "grep", "MemTotal"})).run()
+ Expect(err).To(BeNil())
+ Expect(sshSession2).To(Exit(0))
+ Expect(sshSession2.outputToString()).To(ContainSubstring("3824"))
+ })
+
+ It("no settings should change if no flags", func() {
+ name := randomString(12)
+ i := new(initMachine)
+ session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
+ Expect(err).To(BeNil())
+ Expect(session).To(Exit(0))
+
+ set := setMachine{}
+ setSession, err := mb.setName(name).setCmd(&set).run()
+ Expect(err).To(BeNil())
+ Expect(setSession).To(Exit(0))
+
+ s := new(startMachine)
+ startSession, err := mb.setCmd(s).run()
+ Expect(err).To(BeNil())
+ Expect(startSession).To(Exit(0))
+
+ ssh2 := sshMachine{}
+ sshSession2, err := mb.setName(name).setCmd(ssh2.withSSHComand([]string{"lscpu", "|", "grep", "\"CPU(s):\"", "|", "head", "-1"})).run()
+ Expect(err).To(BeNil())
+ Expect(sshSession2).To(Exit(0))
+ Expect(sshSession2.outputToString()).To(ContainSubstring("1"))
+
+ ssh3 := sshMachine{}
+ sshSession3, err := mb.setName(name).setCmd(ssh3.withSSHComand([]string{"sudo", "fdisk", "-l", "|", "grep", "Disk"})).run()
+ Expect(err).To(BeNil())
+ Expect(sshSession3).To(Exit(0))
+ Expect(sshSession3.outputToString()).To(ContainSubstring("100 GiB"))
+ })
+
+})
diff --git a/pkg/machine/e2e/ssh_test.go b/pkg/machine/e2e/ssh_test.go
index 90296fa10..155d39a64 100644
--- a/pkg/machine/e2e/ssh_test.go
+++ b/pkg/machine/e2e/ssh_test.go
@@ -3,6 +3,7 @@ package e2e
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("podman machine ssh", func() {
@@ -23,7 +24,7 @@ var _ = Describe("podman machine ssh", func() {
ssh := sshMachine{}
session, err := mb.setName(name).setCmd(ssh).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(125))
+ Expect(session).To(Exit(125))
// TODO seems like stderr is not being returned; re-enabled when fixed
//Expect(session.outputToString()).To(ContainSubstring("not exist"))
})
@@ -33,14 +34,14 @@ var _ = Describe("podman machine ssh", func() {
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).To(Exit(0))
ssh := sshMachine{}
sshSession, err := mb.setName(name).setCmd(ssh).run()
Expect(err).To(BeNil())
// TODO seems like stderr is not being returned; re-enabled when fixed
//Expect(sshSession.outputToString()).To(ContainSubstring("is not running"))
- Expect(sshSession.ExitCode()).To(Equal(125))
+ Expect(sshSession).To(Exit(125))
})
It("ssh to running machine and check os-type", func() {
@@ -48,12 +49,12 @@ var _ = Describe("podman machine ssh", func() {
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).To(Exit(0))
ssh := sshMachine{}
sshSession, err := mb.setName(name).setCmd(ssh.withSSHComand([]string{"cat", "/etc/os-release"})).run()
Expect(err).To(BeNil())
- Expect(sshSession.ExitCode()).To(Equal(0))
+ Expect(sshSession).To(Exit(0))
Expect(sshSession.outputToString()).To(ContainSubstring("Fedora CoreOS"))
})
})
diff --git a/pkg/machine/e2e/start_test.go b/pkg/machine/e2e/start_test.go
index 1cda0e8f1..1de66eb9a 100644
--- a/pkg/machine/e2e/start_test.go
+++ b/pkg/machine/e2e/start_test.go
@@ -4,6 +4,7 @@ import (
"github.com/containers/podman/v4/pkg/machine"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("podman machine start", func() {
@@ -22,11 +23,11 @@ var _ = Describe("podman machine start", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).To(Exit(0))
s := new(startMachine)
startSession, err := mb.setCmd(s).run()
Expect(err).To(BeNil())
- Expect(startSession.ExitCode()).To(Equal(0))
+ Expect(startSession).To(Exit(0))
info, ec, err := mb.toQemuInspectInfo()
Expect(err).To(BeNil())
diff --git a/pkg/machine/e2e/stop_test.go b/pkg/machine/e2e/stop_test.go
index 5dee6a345..0c27045a6 100644
--- a/pkg/machine/e2e/stop_test.go
+++ b/pkg/machine/e2e/stop_test.go
@@ -3,6 +3,7 @@ package e2e
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("podman machine stop", func() {
@@ -23,24 +24,24 @@ var _ = Describe("podman machine stop", func() {
reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
session, err := mb.setName(reallyLongName).setCmd(&i).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(125))
+ Expect(session).To(Exit(125))
})
It("Stop running machine", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath).withNow()).run()
Expect(err).To(BeNil())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).To(Exit(0))
stop := new(stopMachine)
// Removing a running machine should fail
stopSession, err := mb.setCmd(stop).run()
Expect(err).To(BeNil())
- Expect(stopSession.ExitCode()).To(Equal(0))
+ Expect(stopSession).To(Exit(0))
// Stopping it again should not result in an error
stopAgain, err := mb.setCmd(stop).run()
Expect(err).To(BeNil())
- Expect(stopAgain.ExitCode()).To(BeZero())
+ Expect(stopAgain).To(Exit((0)))
})
})
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..3f92881fa 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
}
@@ -67,13 +68,19 @@ func TestNewMachineFile(t *testing.T) {
p := "/var/tmp/podman/my.sock"
longp := filepath.Join(longTemp, utils.RandomString(100), "my.sock")
- os.MkdirAll(filepath.Dir(longp), 0755)
- f, _ := os.Create(longp)
- f.Close()
+ err = os.MkdirAll(filepath.Dir(longp), 0755)
+ if err != nil {
+ panic(err)
+ }
+ f, err := os.Create(longp)
+ if err != nil {
+ panic(err)
+ }
+ _ = f.Close()
sym := "my.sock"
longSym := filepath.Join(homedir, ".podman", sym)
- m := MachineFile{
+ m := machine.VMFile{
Path: p,
Symlink: nil,
}
@@ -84,7 +91,7 @@ func TestNewMachineFile(t *testing.T) {
tests := []struct {
name string
args args
- want *MachineFile
+ want *machine.VMFile
wantErr bool
}{
{
@@ -96,7 +103,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,19 +121,20 @@ 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 {
+ tt := tt
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
+ got, err := machine.NewMachineFile(tt.args.path, tt.args.symlink)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("NewMachineFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
- if !reflect.DeepEqual(got, tt.want) { //nolint: scopelint
- t.Errorf("NewMachineFile() got = %v, want %v", got, tt.want) //nolint: scopelint
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("NewMachineFile() got = %v, want %v", got, tt.want)
}
})
}
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index 5481bad29..91e15c2af 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
@@ -390,25 +389,9 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err != nil {
return false, err
}
- // Resize the disk image to input disk size
- // only if the virtualdisk size is less than
- // the given disk size
- if opts.DiskSize<<(10*3) > originalDiskSize {
- // Find the qemu executable
- cfg, err := config.Default()
- if err != nil {
- return false, err
- }
- resizePath, err := cfg.FindHelperBinary("qemu-img", true)
- if err != nil {
- return false, err
- }
- resize := exec.Command(resizePath, []string{"resize", v.getImageFile(), strconv.Itoa(int(opts.DiskSize)) + "G"}...)
- resize.Stdout = os.Stdout
- resize.Stderr = os.Stderr
- if err := resize.Run(); err != nil {
- return false, errors.Errorf("resizing image: %q", err)
- }
+
+ if err := v.resizeDisk(opts.DiskSize, originalDiskSize>>(10*3)); err != nil {
+ return false, err
}
// If the user provides an ignition file, we need to
// copy it into the conf dir
@@ -432,14 +415,14 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
return err == nil, err
}
-func (v *MachineVM) Set(_ string, opts machine.SetOptions) error {
- if v.Rootful == opts.Rootful {
- return nil
- }
+func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) {
+ // If one setting fails to be applied, the others settings will not fail and still be applied.
+ // The setting(s) that failed to be applied will have its errors returned in setErrors
+ var setErrors []error
state, err := v.State(false)
if err != nil {
- return err
+ return setErrors, err
}
if state == machine.Running {
@@ -447,26 +430,45 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) error {
if v.Name != machine.DefaultMachineName {
suffix = " " + v.Name
}
- return errors.Errorf("cannot change setting while the vm is running, run 'podman machine stop%s' first", suffix)
+ return setErrors, errors.Errorf("cannot change settings while the vm is running, run 'podman machine stop%s' first", suffix)
}
- changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
- if err != nil {
- return err
+ if opts.Rootful != nil && v.Rootful != *opts.Rootful {
+ if err := v.setRootful(*opts.Rootful); err != nil {
+ setErrors = append(setErrors, errors.Wrapf(err, "failed to set rootful option"))
+ } else {
+ v.Rootful = *opts.Rootful
+ }
}
- if changeCon {
- newDefault := v.Name
- if opts.Rootful {
- newDefault += "-root"
- }
- if err := machine.ChangeDefault(newDefault); err != nil {
- return err
+ if opts.CPUs != nil && v.CPUs != *opts.CPUs {
+ v.CPUs = *opts.CPUs
+ v.editCmdLine("-smp", strconv.Itoa(int(v.CPUs)))
+ }
+
+ if opts.Memory != nil && v.Memory != *opts.Memory {
+ v.Memory = *opts.Memory
+ v.editCmdLine("-m", strconv.Itoa(int(v.Memory)))
+ }
+
+ if opts.DiskSize != nil && v.DiskSize != *opts.DiskSize {
+ if err := v.resizeDisk(*opts.DiskSize, v.DiskSize); err != nil {
+ setErrors = append(setErrors, errors.Wrapf(err, "failed to resize disk"))
+ } else {
+ v.DiskSize = *opts.DiskSize
}
}
- v.Rootful = opts.Rootful
- return v.writeConfig()
+ err = v.writeConfig()
+ if err != nil {
+ setErrors = append(setErrors, err)
+ }
+
+ if len(setErrors) > 0 {
+ return setErrors, setErrors[0]
+ }
+
+ return setErrors, nil
}
// Start executes the qemu command line and forks it
@@ -482,12 +484,11 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if err := v.writeConfig(); err != nil {
return fmt.Errorf("writing JSON file: %w", err)
}
- defer func() error {
+ defer func() {
v.Starting = false
if err := v.writeConfig(); err != nil {
- return fmt.Errorf("writing JSON file: %w", err)
+ logrus.Errorf("Writing JSON file: %v", err)
}
- return nil
}()
if v.isIncompatible() {
logrus.Errorf("machine %q is incompatible with this release of podman and needs to be recreated, starting for recovery only", v.Name)
@@ -806,7 +807,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
}
@@ -1063,11 +1064,11 @@ func getVMInfos() ([]*machine.ListResponse, error) {
return err
}
- if !vm.LastUp.IsZero() {
+ if !vm.LastUp.IsZero() { // this means we have already written a time to the config
listEntry.LastUp = vm.LastUp
- } else {
+ } else { // else we just created the machine AKA last up = created time
listEntry.LastUp = vm.Created
- vm.Created = time.Now()
+ vm.LastUp = listEntry.LastUp
if err := vm.writeConfig(); err != nil {
return err
}
@@ -1235,14 +1236,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 {
@@ -1251,7 +1252,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
}
@@ -1265,7 +1266,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
}
@@ -1283,7 +1284,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
}
@@ -1460,5 +1461,85 @@ 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.
+func (v *MachineVM) resizeDisk(diskSize uint64, oldSize uint64) error {
+ // Resize the disk image to input disk size
+ // only if the virtualdisk size is less than
+ // the given disk size
+ if diskSize < oldSize {
+ return errors.Errorf("new disk size must be larger than current disk size: %vGB", oldSize)
+ }
+
+ // Find the qemu executable
+ cfg, err := config.Default()
+ if err != nil {
+ return err
+ }
+ resizePath, err := cfg.FindHelperBinary("qemu-img", true)
+ if err != nil {
+ return err
+ }
+ resize := exec.Command(resizePath, []string{"resize", v.getImageFile(), strconv.Itoa(int(diskSize)) + "G"}...)
+ resize.Stdout = os.Stdout
+ resize.Stderr = os.Stderr
+ if err := resize.Run(); err != nil {
+ return errors.Errorf("resizing image: %q", err)
+ }
+
+ return nil
+}
+
+func (v *MachineVM) setRootful(rootful bool) error {
+ changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
+ if err != nil {
+ return err
+ }
+
+ if changeCon {
+ newDefault := v.Name
+ if rootful {
+ newDefault += "-root"
+ }
+ err := machine.ChangeDefault(newDefault)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (v *MachineVM) editCmdLine(flag string, value string) {
+ found := false
+ for i, val := range v.CmdLine {
+ if val == flag {
+ found = true
+ v.CmdLine[i+1] = value
+ break
+ }
+ }
+ if !found {
+ v.CmdLine = append(v.CmdLine, []string{flag, value}...)
+ }
}
diff --git a/pkg/machine/qemu/machine_test.go b/pkg/machine/qemu/machine_test.go
new file mode 100644
index 000000000..62ca6068a
--- /dev/null
+++ b/pkg/machine/qemu/machine_test.go
@@ -0,0 +1,17 @@
+package qemu
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestEditCmd(t *testing.T) {
+ vm := new(MachineVM)
+ vm.CmdLine = []string{"command", "-flag", "value"}
+
+ vm.editCmdLine("-flag", "newvalue")
+ vm.editCmdLine("-anotherflag", "anothervalue")
+
+ require.Equal(t, vm.CmdLine, []string{"command", "-flag", "newvalue", "-anotherflag", "anothervalue"})
+}
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
index f57dbd299..bc2ac7864 100644
--- a/pkg/machine/wsl/machine.go
+++ b/pkg/machine/wsl/machine.go
@@ -18,7 +18,6 @@ import (
"strings"
"time"
- "github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/utils"
"github.com/containers/storage/pkg/homedir"
@@ -153,20 +152,22 @@ const (
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
+ // ConfigPath is the path to the configuration file
+ 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 string
// ImagePath is the fq path to
ImagePath string
+ // LastUp contains the last recorded uptime
+ LastUp time.Time
// Name of the vm
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
Rootful bool
+ // SSH identity, username, etc
+ machine.SSHConfig
}
type ExitCodeError struct {
@@ -181,16 +182,22 @@ func GetWSLProvider() machine.Provider {
return wslProvider
}
-// NewMachine initializes an instance of a virtual machine based on the qemu
-// virtualization.
+// NewMachine initializes an instance of a wsl machine
func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vm := new(MachineVM)
if len(opts.Name) > 0 {
vm.Name = opts.Name
}
+ configPath, err := getConfigPath(opts.Name)
+ if err != nil {
+ return vm, err
+ }
+ vm.ConfigPath = configPath
vm.ImagePath = opts.ImagePath
vm.RemoteUsername = opts.Username
+ vm.Created = time.Now()
+ vm.LastUp = vm.Created
// Add a random port for ssh
port, err := utils.GetRandomPort()
@@ -202,25 +209,69 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
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
// and returns a vm instance
func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
- vm := new(MachineVM)
- vmConfigDir, err := machine.GetConfDir(vmtype)
+ configPath, err := getConfigPath(name)
if err != nil {
return nil, err
}
- b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json"))
- if os.IsNotExist(err) {
- return nil, errors.Wrap(machine.ErrNoSuchVM, name)
- }
+
+ 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)
+ b, err := os.ReadFile(configPath)
if err != nil {
- return nil, err
+ if errors.Is(err, os.ErrNotExist) {
+ return nil, errors.Wrap(machine.ErrNoSuchVM, name)
+ }
+ return vm, err
}
err = json.Unmarshal(b, vm)
+ if err == nil && vm.Created.IsZero() {
+ err = vm.migrate40(configPath)
+ }
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
// other verbs (start, stop)
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 {
- vmConfigDir, err := machine.GetConfDir(vmtype)
- if err != nil {
- return err
- }
-
- jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
+ jsonFile := v.ConfigPath
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
@@ -736,28 +782,34 @@ func pipeCmdPassThrough(name string, input string, arg ...string) error {
return cmd.Run()
}
-func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
- if v.Rootful == opts.Rootful {
- return nil
+func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) {
+ // If one setting fails to be applied, the others settings will not fail and still be applied.
+ // The setting(s) that failed to be applied will have its errors returned in setErrors
+ var setErrors []error
+
+ if opts.Rootful != nil && v.Rootful != *opts.Rootful {
+ err := v.setRootful(*opts.Rootful)
+ if err != nil {
+ setErrors = append(setErrors, errors.Wrapf(err, "error setting rootful option"))
+ } else {
+ v.Rootful = *opts.Rootful
+ }
}
- changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
- if err != nil {
- return err
+ if opts.CPUs != nil {
+ setErrors = append(setErrors, errors.Errorf("changing CPUs not suppored for WSL machines"))
}
- if changeCon {
- newDefault := v.Name
- if opts.Rootful {
- newDefault += "-root"
- }
- if err := machine.ChangeDefault(newDefault); err != nil {
- return err
- }
+ if opts.Memory != nil {
+ setErrors = append(setErrors, errors.Errorf("changing memory not suppored for WSL machines"))
+
}
- v.Rootful = opts.Rootful
- return v.writeConfig()
+ if opts.DiskSize != nil {
+ setErrors = append(setErrors, errors.Errorf("changing Disk Size not suppored for WSL machines"))
+ }
+
+ return setErrors, v.writeConfig()
}
func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
@@ -804,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) {
@@ -999,6 +1052,8 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
return errors.Errorf("%q is not running", v.Name)
}
+ _, _, _ = v.updateTimeStamps(true)
+
if err := stopWinProxy(v); err != nil {
fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
}
@@ -1026,10 +1081,12 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
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) {
- return "", define.ErrNotImplemented
+ if v.isRunning() {
+ return machine.Running, nil
+ }
+
+ return machine.Stopped, nil
}
func stopWinProxy(v *MachineVM) error {
@@ -1204,14 +1261,9 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
var listed []*machine.ListResponse
if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
- vm := new(MachineVM)
if strings.HasSuffix(d.Name(), ".json") {
- fullPath := filepath.Join(vmConfigDir, d.Name())
- b, err := ioutil.ReadFile(fullPath)
- if err != nil {
- return err
- }
- err = json.Unmarshal(b, vm)
+ path := filepath.Join(vmConfigDir, d.Name())
+ vm, err := readAndMigrate(path, strings.TrimSuffix(d.Name(), ".json"))
if err != nil {
return err
}
@@ -1223,15 +1275,13 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
listEntry.CPUs, _ = getCPUs(vm)
listEntry.Memory, _ = getMem(vm)
listEntry.DiskSize = getDiskSize(vm)
- fi, err := os.Stat(fullPath)
- if err != nil {
- return err
- }
- listEntry.CreatedAt = fi.ModTime()
- listEntry.LastUp = getLastStart(vm, fi.ModTime())
- if vm.isRunning() {
- listEntry.Running = true
- }
+ listEntry.RemoteUsername = vm.RemoteUsername
+ listEntry.Port = vm.Port
+ listEntry.IdentityPath = vm.IdentityPath
+
+ running := vm.isRunning()
+ listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running)
+ listEntry.Running = running
listed = append(listed, listEntry)
}
@@ -1242,6 +1292,16 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
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 {
vmDataDir, err := machine.GetDataDir(vmtype)
if err != nil {
@@ -1256,36 +1316,6 @@ func getDiskSize(vm *MachineVM) uint64 {
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) {
dist := toDist(vm.Name)
if run, _ := isWSLRunning(dist); !run {
@@ -1362,3 +1392,53 @@ func (p *Provider) IsValidVMName(name string) (bool, error) {
func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
return false, "", nil
}
+
+func (v *MachineVM) setRootful(rootful bool) error {
+ changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
+ if err != nil {
+ return err
+ }
+
+ if changeCon {
+ newDefault := v.Name
+ if rootful {
+ newDefault += "-root"
+ }
+ err := machine.ChangeDefault(newDefault)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// 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
+ }
+
+ 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
+}