diff options
Diffstat (limited to 'pkg/machine')
-rw-r--r-- | pkg/machine/config.go | 29 | ||||
-rw-r--r-- | pkg/machine/e2e/inspect_test.go | 12 | ||||
-rw-r--r-- | pkg/machine/e2e/machine_test.go | 11 | ||||
-rw-r--r-- | pkg/machine/qemu/config_test.go | 35 | ||||
-rw-r--r-- | pkg/machine/qemu/machine.go | 84 | ||||
-rw-r--r-- | pkg/machine/wsl/machine.go | 273 |
6 files changed, 338 insertions, 106 deletions
diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 9a0ce757a..d34776714 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -52,6 +52,7 @@ type Provider interface { List(opts ListOptions) ([]*ListResponse, error) IsValidVMName(name string) (bool, error) CheckExclusiveActiveVM() (bool, string, error) + RemoveAndCleanMachines() error } type RemoteConnectionType string @@ -170,11 +171,11 @@ func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url // GetDataDir returns the filepath where vm images should // live for podman-machine. func GetDataDir(vmType string) (string, error) { - data, err := homedir.GetDataHome() + dataDirPrefix, err := DataDirPrefix() if err != nil { return "", err } - dataDir := filepath.Join(data, "containers", "podman", "machine", vmType) + dataDir := filepath.Join(dataDirPrefix, vmType) if _, err := os.Stat(dataDir); !os.IsNotExist(err) { return dataDir, nil } @@ -182,14 +183,24 @@ func GetDataDir(vmType string) (string, error) { return dataDir, mkdirErr } +// DataDirPrefix returns the path prefix for all machine data files +func DataDirPrefix() (string, error) { + data, err := homedir.GetDataHome() + if err != nil { + return "", err + } + dataDir := filepath.Join(data, "containers", "podman", "machine") + return dataDir, nil +} + // GetConfigDir returns the filepath to where configuration // files for podman-machine should live func GetConfDir(vmType string) (string, error) { - conf, err := homedir.GetConfigHome() + confDirPrefix, err := ConfDirPrefix() if err != nil { return "", err } - confDir := filepath.Join(conf, "containers", "podman", "machine", vmType) + confDir := filepath.Join(confDirPrefix, vmType) if _, err := os.Stat(confDir); !os.IsNotExist(err) { return confDir, nil } @@ -197,6 +208,16 @@ func GetConfDir(vmType string) (string, error) { return confDir, mkdirErr } +// ConfDirPrefix returns the path prefix for all machine config files +func ConfDirPrefix() (string, error) { + conf, err := homedir.GetConfigHome() + if err != nil { + return "", err + } + confDir := filepath.Join(conf, "containers", "podman", "machine") + return confDir, nil +} + // ResourceConfig describes physical attributes of the machine type ResourceConfig struct { // CPUs to be assigned to the VM diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go index e282dd21d..b34285dd8 100644 --- a/pkg/machine/e2e/inspect_test.go +++ b/pkg/machine/e2e/inspect_test.go @@ -43,9 +43,11 @@ var _ = Describe("podman machine stop", func() { Expect(foo2).To(Exit(0)) inspect := new(inspectMachine) + inspect = inspect.withFormat("{{.Name}}") inspectSession, err := mb.setName("foo1").setCmd(inspect).run() Expect(err).To(BeNil()) Expect(inspectSession).To(Exit(0)) + Expect(inspectSession.Bytes()).To(ContainSubstring("foo1")) type fakeInfos struct { Status string @@ -56,13 +58,13 @@ var _ = Describe("podman machine stop", func() { Expect(err).ToNot(HaveOccurred()) Expect(len(infos)).To(Equal(2)) - //rm := new(rmMachine) - //// Must manually clean up due to multiple names - //for _, name := range []string{"foo1", "foo2"} { + // rm := new(rmMachine) + // // Must manually clean up due to multiple names + // for _, name := range []string{"foo1", "foo2"} { // mb.setName(name).setCmd(rm.withForce()).run() // mb.names = []string{} - //} - //mb.names = []string{} + // } + // mb.names = []string{} }) }) diff --git a/pkg/machine/e2e/machine_test.go b/pkg/machine/e2e/machine_test.go index 2b3b60b2b..657014b05 100644 --- a/pkg/machine/e2e/machine_test.go +++ b/pkg/machine/e2e/machine_test.go @@ -23,14 +23,20 @@ func TestMain(m *testing.M) { const ( defaultStream string = "podman-testing" - tmpDir string = "/var/tmp" ) var ( + tmpDir = "/var/tmp" fqImageName string suiteImageName string ) +func init() { + if value, ok := os.LookupEnv("TMPDIR"); ok { + tmpDir = value + } +} + // TestLibpod ginkgo master function func TestMachine(t *testing.T) { RegisterFailHandler(Fail) @@ -70,7 +76,8 @@ var _ = SynchronizedAfterSuite(func() {}, }) func setup() (string, *machineTestBuilder) { - homeDir, err := ioutil.TempDir("/var/tmp", "podman_test") + // Set TMPDIR if this needs a new directory + homeDir, err := ioutil.TempDir("", "podman_test") if err != nil { Fail(fmt.Sprintf("failed to create home directory: %q", err)) } diff --git a/pkg/machine/qemu/config_test.go b/pkg/machine/qemu/config_test.go index 0fbb5b3bf..4d96ec6e7 100644 --- a/pkg/machine/qemu/config_test.go +++ b/pkg/machine/qemu/config_test.go @@ -52,25 +52,23 @@ func TestMachineFile_GetPath(t *testing.T) { func TestNewMachineFile(t *testing.T) { empty := "" - homedir, err := os.MkdirTemp("/tmp", "homedir") - if err != nil { - panic(err) - } - defer os.RemoveAll(homedir) - longTemp, err := os.MkdirTemp("/tmp", "tmpdir") - if err != nil { - panic(err) - } - defer os.RemoveAll(longTemp) + homedir := t.TempDir() + longTemp := t.TempDir() oldhome := os.Getenv("HOME") os.Setenv("HOME", homedir) //nolint: tenv defer os.Setenv("HOME", oldhome) 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) @@ -120,14 +118,15 @@ func TestNewMachineFile(t *testing.T) { }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { - 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 + 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 35eea5fb4..6e36b0886 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -484,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) @@ -526,10 +525,11 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { time.Sleep(wait) wait++ } - defer qemuSocketConn.Close() if err != nil { return err } + defer qemuSocketConn.Close() + fd, err := qemuSocketConn.(*net.UnixConn).File() if err != nil { return err @@ -1544,3 +1544,79 @@ func (v *MachineVM) editCmdLine(flag string, value string) { v.CmdLine = append(v.CmdLine, []string{flag, value}...) } } + +// RemoveAndCleanMachines removes all machine and cleans up any other files associatied with podman machine +func (p *Provider) RemoveAndCleanMachines() error { + var ( + vm machine.VM + listResponse []*machine.ListResponse + opts machine.ListOptions + destroyOptions machine.RemoveOptions + ) + destroyOptions.Force = true + var prevErr error + + listResponse, err := p.List(opts) + if err != nil { + return err + } + + for _, mach := range listResponse { + vm, err = p.LoadVMByName(mach.Name) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + _, remove, err := vm.Remove(mach.Name, destroyOptions) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + if err := remove(); err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + } + + // Clean leftover files in data dir + dataDir, err := machine.DataDirPrefix() + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + err := os.RemoveAll(dataDir) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + + // Clean leftover files in conf dir + confDir, err := machine.ConfDirPrefix() + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + err := os.RemoveAll(confDir) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + return prevErr +} diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index addcb7c8a..57fb36fc9 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 + // 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 { @@ -810,7 +856,8 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { } } - return markStart(name) + _, _, err = v.updateTimeStamps(true) + return err } func launchWinProxy(v *MachineVM) (bool, string, error) { @@ -1005,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()) } @@ -1032,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 { @@ -1210,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 } @@ -1229,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) } @@ -1248,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 { @@ -1262,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 { @@ -1388,6 +1412,109 @@ func (v *MachineVM) setRootful(rootful bool) error { return nil } +// Inspect returns verbose detail about the machine func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { - return nil, define.ErrNotImplemented + state, err := v.State(false) + if err != nil { + return nil, err + } + + created, lastUp, _ := v.updateTimeStamps(state == machine.Running) + + return &machine.InspectInfo{ + ConfigPath: machine.VMFile{Path: v.ConfigPath}, + Created: created, + Image: machine.ImageConfig{ + ImagePath: machine.VMFile{Path: v.ImagePath}, + ImageStream: v.ImageStream, + }, + LastUp: lastUp, + Name: v.Name, + Resources: v.getResources(), + SSHConfig: v.SSHConfig, + State: state, + }, nil +} + +func (v *MachineVM) getResources() (resources machine.ResourceConfig) { + resources.CPUs, _ = getCPUs(v) + resources.Memory, _ = getMem(v) + resources.DiskSize = getDiskSize(v) + return +} + +// RemoveAndCleanMachines removes all machine and cleans up any other files associatied with podman machine +func (p *Provider) RemoveAndCleanMachines() error { + var ( + vm machine.VM + listResponse []*machine.ListResponse + opts machine.ListOptions + destroyOptions machine.RemoveOptions + ) + destroyOptions.Force = true + var prevErr error + + listResponse, err := p.List(opts) + if err != nil { + return err + } + + for _, mach := range listResponse { + vm, err = p.LoadVMByName(mach.Name) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + _, remove, err := vm.Remove(mach.Name, destroyOptions) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + if err := remove(); err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + } + + // Clean leftover files in data dir + dataDir, err := machine.DataDirPrefix() + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + err := os.RemoveAll(dataDir) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + + // Clean leftover files in conf dir + confDir, err := machine.ConfDirPrefix() + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + err := os.RemoveAll(confDir) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + return prevErr } |