From e5a4f00b7d1a15441f1d42a0cbabd6339f746ad8 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Wed, 22 Nov 2023 07:59:25 -0600 Subject: [PATCH] Podman 5 machine config file - Step 1 The following PR is the very first step in what will a series of steps to apply a "common" machine configuration file to all providers. Function names, method names, struct names, and field names are all up for debate. The purpose of this PR is to offer a glimpse at the direction we intend to take. This PR also contains temporary structs (i.e. aThing) that are not exported. These are merely placeholders. The configuration work in this PR is also unused of yet. But the code is compiled. Once merged, we can begin the next step of development. [NO NEW TESTS NEEDED] Signed-off-by: Brent Baude --- cmd/podman/compose.go | 3 +- pkg/machine/applehv/config.go | 11 +- pkg/machine/applehv/machine.go | 55 ++-- .../applehv/{rest.go => vfkit/config.go} | 36 +-- .../applehv/{rest_config.go => vfkit/rest.go} | 14 +- pkg/machine/config.go | 66 +---- pkg/machine/define/config.go | 3 + pkg/machine/define/state.go | 15 ++ pkg/machine/e2e/init_test.go | 3 +- pkg/machine/e2e/start_test.go | 6 +- pkg/machine/hyperv/config.go | 10 +- pkg/machine/hyperv/machine.go | 52 ++-- pkg/machine/hyperv/{ => vsock}/vsock.go | 7 +- pkg/machine/ignition.go | 22 +- pkg/machine/qemu/command.go | 106 -------- pkg/machine/qemu/command/command.go | 236 ++++++++++++++++++ pkg/machine/qemu/command/command_test.go | 94 +++++++ .../qemu/{ => command}/qemu_command_test.go | 2 +- pkg/machine/qemu/config.go | 77 +----- pkg/machine/qemu/config_test.go | 16 +- pkg/machine/qemu/machine.go | 104 +++----- pkg/machine/qemu/machine_test.go | 91 +------ pkg/machine/{ => sockets}/sockets.go | 2 +- pkg/machine/vmconfigs/config.go | 142 +++++++++++ pkg/machine/vmconfigs/config_darwin.go | 15 ++ pkg/machine/vmconfigs/config_freebsd.go | 7 + pkg/machine/vmconfigs/config_linux.go | 14 ++ pkg/machine/vmconfigs/config_windows.go | 21 ++ pkg/machine/volumes.go | 6 +- pkg/machine/wsl/machine.go | 13 +- 30 files changed, 721 insertions(+), 528 deletions(-) rename pkg/machine/applehv/{rest.go => vfkit/config.go} (71%) rename pkg/machine/applehv/{rest_config.go => vfkit/rest.go} (85%) create mode 100644 pkg/machine/define/config.go create mode 100644 pkg/machine/define/state.go rename pkg/machine/hyperv/{ => vsock}/vsock.go (98%) delete mode 100644 pkg/machine/qemu/command.go create mode 100644 pkg/machine/qemu/command/command.go create mode 100644 pkg/machine/qemu/command/command_test.go rename pkg/machine/qemu/{ => command}/qemu_command_test.go (99%) rename pkg/machine/{ => sockets}/sockets.go (99%) create mode 100644 pkg/machine/vmconfigs/config.go create mode 100644 pkg/machine/vmconfigs/config_darwin.go create mode 100644 pkg/machine/vmconfigs/config_freebsd.go create mode 100644 pkg/machine/vmconfigs/config_linux.go create mode 100644 pkg/machine/vmconfigs/config_windows.go diff --git a/cmd/podman/compose.go b/cmd/podman/compose.go index 5f4bc64d6f..5dff6150dc 100644 --- a/cmd/podman/compose.go +++ b/cmd/podman/compose.go @@ -19,6 +19,7 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/machine" + "github.com/containers/podman/v4/pkg/machine/define" "github.com/containers/podman/v4/pkg/machine/provider" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -184,7 +185,7 @@ func composeDockerHost() (string, error) { if err != nil { return "", fmt.Errorf("inspecting machine: %w", err) } - if info.State != machine.Running { + if info.State != define.Running { return "", fmt.Errorf("machine %s is not running but in state %s", item.Name, info.State) } if machineProvider.VMType() == machine.WSLVirt { diff --git a/pkg/machine/applehv/config.go b/pkg/machine/applehv/config.go index c04b43e22a..91e5cc899a 100644 --- a/pkg/machine/applehv/config.go +++ b/pkg/machine/applehv/config.go @@ -13,6 +13,7 @@ import ( "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/compression" "github.com/containers/podman/v4/pkg/machine/define" + "github.com/containers/podman/v4/pkg/machine/vmconfigs" vfConfig "github.com/crc-org/vfkit/pkg/config" "github.com/docker/go-units" "golang.org/x/sys/unix" @@ -76,10 +77,10 @@ func (v AppleHVVirtualization) List(opts machine.ListOptions) ([]*machine.ListRe } for _, mm := range mms { - vmState, err := mm.Vfkit.state() + vmState, err := mm.Vfkit.State() if err != nil { if errors.Is(err, unix.ECONNREFUSED) { - vmState = machine.Stopped + vmState = define.Stopped } else { return nil, err } @@ -89,8 +90,8 @@ func (v AppleHVVirtualization) List(opts machine.ListOptions) ([]*machine.ListRe Name: mm.Name, CreatedAt: mm.Created, LastUp: mm.LastUp, - Running: vmState == machine.Running, - Starting: vmState == machine.Starting, + Running: vmState == define.Running, + Starting: vmState == define.Starting, Stream: mm.ImageStream, VMType: machine.AppleHvVirt.String(), CPUs: mm.CPUs, @@ -140,7 +141,7 @@ func (v AppleHVVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, // Set creation time m.Created = time.Now() - m.ResourceConfig = machine.ResourceConfig{ + m.ResourceConfig = vmconfigs.ResourceConfig{ CPUs: opts.CPUS, DiskSize: opts.DiskSize, // Diskpath will be needed diff --git a/pkg/machine/applehv/machine.go b/pkg/machine/applehv/machine.go index 6e628c873b..c3f8d812fc 100644 --- a/pkg/machine/applehv/machine.go +++ b/pkg/machine/applehv/machine.go @@ -21,7 +21,10 @@ import ( "github.com/containers/common/pkg/config" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/containers/podman/v4/pkg/machine" + "github.com/containers/podman/v4/pkg/machine/applehv/vfkit" "github.com/containers/podman/v4/pkg/machine/define" + "github.com/containers/podman/v4/pkg/machine/sockets" + "github.com/containers/podman/v4/pkg/machine/vmconfigs" "github.com/containers/podman/v4/pkg/strongunits" "github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/utils" @@ -43,14 +46,6 @@ const ( apiUpTimeout = 20 * time.Second ) -// VfkitHelper describes the use of vfkit: cmdline and endpoint -type VfkitHelper struct { - LogLevel logrus.Level - Endpoint string - VfkitBinaryPath *define.VMFile - VirtualMachine *vfConfig.VirtualMachine -} - // appleHVReadyUnit is a unit file that sets up the virtual serial device // where when the VM is done configuring, it will send an ack // so a listening host knows it can begin interacting with it @@ -71,19 +66,19 @@ type MacMachine struct { // ConfigPath is the fully qualified path to the configuration file ConfigPath define.VMFile // HostUser contains info about host user - machine.HostUser + vmconfigs.HostUser // ImageConfig describes the bootable image machine.ImageConfig // Mounts is the list of remote filesystems to mount - Mounts []machine.Mount + Mounts []vmconfigs.Mount // Name of VM Name string // ReadySocket tells host when vm is booted ReadySocket define.VMFile // ResourceConfig is physical attrs of the VM - machine.ResourceConfig + vmconfigs.ResourceConfig // SSHConfig for accessing the remote vm - machine.SSHConfig + vmconfigs.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 @@ -91,7 +86,7 @@ type MacMachine struct { // LastUp contains the last recorded uptime LastUp time.Time // The VFKit endpoint where we can interact with the VM - Vfkit VfkitHelper + Vfkit vfkit.VfkitHelper LogPath define.VMFile GvProxyPid define.VMFile GvProxySock define.VMFile @@ -108,7 +103,7 @@ func (m *MacMachine) setGVProxyInfo(runtimeDir string) error { } m.GvProxyPid = *gvProxyPid - return machine.SetSocket(&m.GvProxySock, filepath.Join(runtimeDir, "gvproxy.sock"), nil) + return sockets.SetSocket(&m.GvProxySock, filepath.Join(runtimeDir, "gvproxy.sock"), nil) } // setVfkitInfo stores the default devices, sets the vfkit endpoint, and @@ -138,7 +133,7 @@ func (m *MacMachine) setVfkitInfo(cfg *config.Config, readySocket define.VMFile) // addMountsToVM converts the volumes passed through the CLI to virtio-fs mounts // and adds them to the machine func (m *MacMachine) addMountsToVM(opts machine.InitOptions, virtiofsMnts *[]machine.VirtIoFs) error { - var mounts []machine.Mount + var mounts []vmconfigs.Mount for _, volume := range opts.Volumes { source, target, _, readOnly, err := machine.ParseVolumeFromPath(volume) if err != nil { @@ -202,7 +197,7 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) { return false, err } - if err := machine.SetSocket(&m.ReadySocket, machine.ReadySocketPath(runtimeDir, m.Name), nil); err != nil { + if err := sockets.SetSocket(&m.ReadySocket, sockets.ReadySocketPath(runtimeDir, m.Name), nil); err != nil { return false, err } @@ -305,7 +300,7 @@ func (m *MacMachine) removeSystemConnections() error { } func (m *MacMachine) Inspect() (*machine.InspectInfo, error) { - vmState, err := m.Vfkit.state() + vmState, err := m.Vfkit.State() if err != nil { return nil, err } @@ -329,7 +324,7 @@ func (m *MacMachine) Inspect() (*machine.InspectInfo, error) { }, LastUp: m.LastUp, Name: m.Name, - Resources: machine.ResourceConfig{ + Resources: vmconfigs.ResourceConfig{ CPUs: m.CPUs, DiskSize: m.DiskSize, Memory: m.Memory, @@ -367,16 +362,16 @@ func (m *MacMachine) Remove(name string, opts machine.RemoveOptions) (string, fu m.lock.Lock() defer m.lock.Unlock() - vmState, err := m.Vfkit.state() + vmState, err := m.Vfkit.State() if err != nil { return "", nil, err } - if vmState == machine.Running { + if vmState == define.Running { if !opts.Force { return "", nil, &machine.ErrVMRunningCannotDestroyed{Name: m.Name} } - if err := m.Vfkit.stop(true, true); err != nil { + if err := m.Vfkit.Stop(true, true); err != nil { return "", nil, err } defer func() { @@ -430,7 +425,7 @@ func (m *MacMachine) Set(name string, opts machine.SetOptions) ([]error, error) if err != nil { return nil, err } - if vmState != machine.Stopped { + if vmState != define.Stopped { return nil, machine.ErrWrongState } if cpus := opts.CPUs; cpus != nil { @@ -473,7 +468,7 @@ func (m *MacMachine) SSH(name string, opts machine.SSHOptions) error { if err != nil { return err } - if st != machine.Running { + if st != define.Running { return fmt.Errorf("vm %q is not running", m.Name) } username := opts.Username @@ -561,7 +556,7 @@ func (m *MacMachine) Start(name string, opts machine.StartOptions) error { return err } - if st == machine.Running { + if st == define.Running { return machine.ErrVMAlreadyRunning } @@ -664,7 +659,7 @@ func (m *MacMachine) Start(name string, opts machine.StartOptions) error { logrus.Debug("waiting for ready notification") readyChan := make(chan error) - go machine.ListenAndWaitOnSocket(readyChan, readyListen) + go sockets.ListenAndWaitOnSocket(readyChan, readyListen) if err := cmd.Start(); err != nil { return err @@ -715,8 +710,8 @@ func (m *MacMachine) Start(name string, opts machine.StartOptions) error { return nil } -func (m *MacMachine) State(_ bool) (machine.Status, error) { - vmStatus, err := m.Vfkit.state() +func (m *MacMachine) State(_ bool) (define.Status, error) { + vmStatus, err := m.Vfkit.State() if err != nil { return "", err } @@ -732,7 +727,7 @@ func (m *MacMachine) Stop(name string, opts machine.StopOptions) error { return err } - if vmState != machine.Running { + if vmState != define.Running { return nil } @@ -742,7 +737,7 @@ func (m *MacMachine) Stop(name string, opts machine.StopOptions) error { } }() - return m.Vfkit.stop(false, true) + return m.Vfkit.Stop(false, true) } // getVMConfigPath is a simple wrapper for getting the fully-qualified @@ -845,7 +840,7 @@ func getVMInfos() ([]*machine.ListResponse, error) { if err != nil { return err } - listEntry.Running = vmState == machine.Running + listEntry.Running = vmState == define.Running listEntry.LastUp = vm.LastUp listed = append(listed, listEntry) diff --git a/pkg/machine/applehv/rest.go b/pkg/machine/applehv/vfkit/config.go similarity index 71% rename from pkg/machine/applehv/rest.go rename to pkg/machine/applehv/vfkit/config.go index a96834541f..b3fa72accc 100644 --- a/pkg/machine/applehv/rest.go +++ b/pkg/machine/applehv/vfkit/config.go @@ -1,7 +1,7 @@ //go:build darwin // +build darwin -package applehv +package vfkit import ( "bytes" @@ -12,14 +12,13 @@ import ( "net/http" "time" - "github.com/containers/podman/v4/pkg/machine" - "github.com/crc-org/vfkit/pkg/rest/define" + "github.com/containers/podman/v4/pkg/machine/define" + "github.com/crc-org/vfkit/pkg/config" + rest "github.com/crc-org/vfkit/pkg/rest/define" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) -type Endpoint string - const ( inspect = "/vm/inspect" state = "/vm/state" @@ -45,8 +44,8 @@ func (vf *VfkitHelper) post(endpoint string, payload io.Reader) (*http.Response, } // getRawState asks vfkit for virtual machine state unmodified (see state()) -func (vf *VfkitHelper) getRawState() (machine.Status, error) { - var response define.VMState +func (vf *VfkitHelper) getRawState() (define.Status, error) { + var response rest.VMState endPoint := vf.Endpoint + state serverResponse, err := vf.get(endPoint, nil) if err != nil { @@ -60,25 +59,24 @@ func (vf *VfkitHelper) getRawState() (machine.Status, error) { return "", err } return ToMachineStatus(response.State) - } // state asks vfkit for the virtual machine state. in case the vfkit // service is not responding, we assume the service is not running // and return a stopped status -func (vf *VfkitHelper) state() (machine.Status, error) { +func (vf *VfkitHelper) State() (define.Status, error) { vmState, err := vf.getRawState() if err == nil { return vmState, err } if errors.Is(err, unix.ECONNREFUSED) { - return machine.Stopped, nil + return define.Stopped, nil } return "", err } -func (vf *VfkitHelper) stateChange(newState define.StateChange) error { - b, err := json.Marshal(define.VMState{State: string(newState)}) +func (vf *VfkitHelper) stateChange(newState rest.StateChange) error { + b, err := json.Marshal(rest.VMState{State: string(newState)}) if err != nil { return err } @@ -87,15 +85,15 @@ func (vf *VfkitHelper) stateChange(newState define.StateChange) error { return err } -func (vf *VfkitHelper) stop(force, wait bool) error { +func (vf *VfkitHelper) Stop(force, wait bool) error { waitDuration := time.Millisecond * 10 // TODO Add ability to wait until stopped if force { - if err := vf.stateChange(define.HardStop); err != nil { + if err := vf.stateChange(rest.HardStop); err != nil { return err } } else { - if err := vf.stateChange(define.Stop); err != nil { + if err := vf.stateChange(rest.Stop); err != nil { return err } } @@ -116,3 +114,11 @@ func (vf *VfkitHelper) stop(force, wait bool) error { } return waitErr } + +// VfkitHelper describes the use of vfkit: cmdline and endpoint +type VfkitHelper struct { + LogLevel logrus.Level + Endpoint string + VfkitBinaryPath *define.VMFile + VirtualMachine *config.VirtualMachine +} diff --git a/pkg/machine/applehv/rest_config.go b/pkg/machine/applehv/vfkit/rest.go similarity index 85% rename from pkg/machine/applehv/rest_config.go rename to pkg/machine/applehv/vfkit/rest.go index 944e72d539..b365310a2c 100644 --- a/pkg/machine/applehv/rest_config.go +++ b/pkg/machine/applehv/vfkit/rest.go @@ -1,15 +1,17 @@ //go:build darwin // +build darwin -package applehv +package vfkit import ( "errors" "fmt" - "github.com/containers/podman/v4/pkg/machine" + "github.com/containers/podman/v4/pkg/machine/define" ) +type Endpoint string + // VZMachineState is what the restful service in vfkit will return type VZMachineState string @@ -26,14 +28,14 @@ const ( VZMachineStateStopping VZMachineState = "VirtualMachineStateStopping" ) -func ToMachineStatus(val string) (machine.Status, error) { +func ToMachineStatus(val string) (define.Status, error) { switch val { case string(VZMachineStateRunning), string(VZMachineStatePausing), string(VZMachineStateResuming), string(VZMachineStateStopping), string(VZMachineStatePaused): - return machine.Running, nil + return define.Running, nil case string(VZMachineStateStopped): - return machine.Stopped, nil + return define.Stopped, nil case string(VZMachineStateStarting): - return machine.Starting, nil + return define.Starting, nil case string(VZMachineStateError): return "", errors.New("machine is in error state") } diff --git a/pkg/machine/config.go b/pkg/machine/config.go index b4324e8755..12383aa688 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -18,6 +18,7 @@ import ( "github.com/containers/common/pkg/machine" "github.com/containers/podman/v4/pkg/machine/compression" "github.com/containers/podman/v4/pkg/machine/define" + "github.com/containers/podman/v4/pkg/machine/vmconfigs" "github.com/containers/storage/pkg/homedir" "github.com/containers/storage/pkg/lockfile" "github.com/sirupsen/logrus" @@ -43,17 +44,7 @@ type InitOptions struct { USBs []string } -type Status = string - const ( - // Running indicates the qemu vm is running. - Running Status = "running" - // Stopped indicates the vm has stopped. - Stopped Status = "stopped" - // Starting indicated the vm is in the process of starting - Starting Status = "starting" - // Unknown means the state is not known - Unknown Status = "unknown" DefaultMachineName string = "podman-machine-default" apiUpTimeout = 20 * time.Second ) @@ -139,7 +130,7 @@ type VM interface { Set(name string, opts SetOptions) ([]error, error) SSH(name string, opts SSHOptions) error Start(name string, opts StartOptions) error - State(bypass bool) (Status, error) + State(bypass bool) (define.Status, error) Stop(name string, opts StopOptions) error } @@ -173,9 +164,9 @@ type InspectInfo struct { Image ImageConfig LastUp time.Time Name string - Resources ResourceConfig - SSHConfig SSHConfig - State Status + Resources vmconfigs.ResourceConfig + SSHConfig vmconfigs.SSHConfig + State define.Status UserModeNetworking bool Rootful bool } @@ -274,33 +265,6 @@ func ConfDirPrefix() (string, error) { return confDir, nil } -type USBConfig struct { - Bus string - DevNumber string - Vendor int - Product int -} - -// 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 - // Usbs - USBs []USBConfig -} - -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 @@ -312,26 +276,6 @@ type ImageConfig struct { ImagePath define.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 - // Whether one of these fields has changed and actions should be taken - Modified bool `json:"HostUserModified"` -} - -// 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 -} - // ConnectionConfig contains connections like sockets, etc. type ConnectionConfig struct { // PodmanSocket is the exported podman service socket diff --git a/pkg/machine/define/config.go b/pkg/machine/define/config.go new file mode 100644 index 0000000000..ba98908be1 --- /dev/null +++ b/pkg/machine/define/config.go @@ -0,0 +1,3 @@ +package define + +const UserCertsTargetPath = "/etc/containers/certs.d" diff --git a/pkg/machine/define/state.go b/pkg/machine/define/state.go new file mode 100644 index 0000000000..1817803466 --- /dev/null +++ b/pkg/machine/define/state.go @@ -0,0 +1,15 @@ +package define + +type Status = string + +// Running indicates the qemu vm is running. +const Running Status = "running" + +// Stopped indicates the vm has stopped. +const Stopped Status = "stopped" + +// Starting indicated the vm is in the process of starting +const Starting Status = "starting" + +// Unknown means the state is not known +const Unknown Status = "unknown" diff --git a/pkg/machine/e2e/init_test.go b/pkg/machine/e2e/init_test.go index db717e3a84..4aab924466 100644 --- a/pkg/machine/e2e/init_test.go +++ b/pkg/machine/e2e/init_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/containers/podman/v4/pkg/machine" + "github.com/containers/podman/v4/pkg/machine/define" "github.com/containers/podman/v4/utils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -108,7 +109,7 @@ var _ = Describe("podman machine init", func() { Expect(ec).To(BeZero()) Expect(inspectBefore).ToNot(BeEmpty()) Expect(inspectAfter).ToNot(BeEmpty()) - Expect(inspectAfter[0].State).To(Equal(machine.Running)) + Expect(inspectAfter[0].State).To(Equal(define.Running)) if isWSL() { // WSL does not use FCOS return diff --git a/pkg/machine/e2e/start_test.go b/pkg/machine/e2e/start_test.go index b62ff9396f..d1001a45d2 100644 --- a/pkg/machine/e2e/start_test.go +++ b/pkg/machine/e2e/start_test.go @@ -1,7 +1,7 @@ package e2e_test import ( - "github.com/containers/podman/v4/pkg/machine" + "github.com/containers/podman/v4/pkg/machine/define" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" @@ -32,7 +32,7 @@ var _ = Describe("podman machine start", func() { info, ec, err := mb.toQemuInspectInfo() Expect(err).ToNot(HaveOccurred()) Expect(ec).To(BeZero()) - Expect(info[0].State).To(Equal(machine.Running)) + Expect(info[0].State).To(Equal(define.Running)) stop := new(stopMachine) stopSession, err := mb.setCmd(stop).run() @@ -77,7 +77,7 @@ var _ = Describe("podman machine start", func() { info, ec, err := mb.toQemuInspectInfo() Expect(err).ToNot(HaveOccurred()) Expect(ec).To(BeZero()) - Expect(info[0].State).To(Equal(machine.Running)) + Expect(info[0].State).To(Equal(define.Running)) startSession, err = mb.setCmd(s).run() Expect(err).ToNot(HaveOccurred()) diff --git a/pkg/machine/hyperv/config.go b/pkg/machine/hyperv/config.go index af4e47ccda..8b8d2336ce 100644 --- a/pkg/machine/hyperv/config.go +++ b/pkg/machine/hyperv/config.go @@ -286,14 +286,14 @@ func handlePrevError(e, prevErr error) error { return e } -func stateConversion(s hypervctl.EnabledState) (machine.Status, error) { +func stateConversion(s hypervctl.EnabledState) (define.Status, error) { switch s { case hypervctl.Enabled: - return machine.Running, nil + return define.Running, nil case hypervctl.Disabled: - return machine.Stopped, nil + return define.Stopped, nil case hypervctl.Starting: - return machine.Starting, nil + return define.Starting, nil } - return machine.Unknown, fmt.Errorf("unknown state: %q", s.String()) + return define.Unknown, fmt.Errorf("unknown state: %q", s.String()) } diff --git a/pkg/machine/hyperv/machine.go b/pkg/machine/hyperv/machine.go index aa5c8a2ebc..272bf04ebb 100644 --- a/pkg/machine/hyperv/machine.go +++ b/pkg/machine/hyperv/machine.go @@ -21,6 +21,8 @@ import ( "github.com/containers/libhvee/pkg/hypervctl" "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/define" + "github.com/containers/podman/v4/pkg/machine/hyperv/vsock" + "github.com/containers/podman/v4/pkg/machine/vmconfigs" "github.com/containers/podman/v4/pkg/strongunits" "github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/utils" @@ -99,21 +101,21 @@ type HyperVMachine struct { // ConfigPath is the fully qualified path to the configuration file ConfigPath define.VMFile // HostUser contains info about host user - machine.HostUser + vmconfigs.HostUser // ImageConfig describes the bootable image machine.ImageConfig // Mounts is the list of remote filesystems to mount - Mounts []machine.Mount + Mounts []vmconfigs.Mount // Name of VM Name string // NetworkVSock is for the user networking - NetworkHVSock HVSockRegistryEntry + NetworkHVSock vsock.HVSockRegistryEntry // ReadySocket tells host when vm is booted - ReadyHVSock HVSockRegistryEntry + ReadyHVSock vsock.HVSockRegistryEntry // ResourceConfig is physical attrs of the VM - machine.ResourceConfig + vmconfigs.ResourceConfig // SSHConfig for accessing the remote vm - machine.SSHConfig + vmconfigs.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 @@ -132,11 +134,11 @@ type HyperVMachine struct { // addNetworkAndReadySocketsToRegistry adds the Network and Ready sockets to the // Windows registry func (m *HyperVMachine) addNetworkAndReadySocketsToRegistry() error { - networkHVSock, err := NewHVSockRegistryEntry(m.Name, Network) + networkHVSock, err := vsock.NewHVSockRegistryEntry(m.Name, vsock.Network) if err != nil { return err } - eventHVSocket, err := NewHVSockRegistryEntry(m.Name, Events) + eventHVSocket, err := vsock.NewHVSockRegistryEntry(m.Name, vsock.Events) if err != nil { return err } @@ -185,7 +187,7 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) { // around to those, would be another : after that. // TODO: Need to support options here for _, mount := range opts.Volumes { - newMount := machine.Mount{} + newMount := vmconfigs.Mount{} splitMount := strings.Split(mount, ":") if len(splitMount) < 3 { @@ -242,7 +244,7 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) { callbackFuncs.Add(m.removeSSHKeys) } - m.ResourceConfig = machine.ResourceConfig{ + m.ResourceConfig = vmconfigs.ResourceConfig{ CPUs: opts.CPUS, DiskSize: opts.DiskSize, Memory: opts.Memory, @@ -367,7 +369,7 @@ func (m *HyperVMachine) Inspect() (*machine.InspectInfo, error) { }, LastUp: m.LastUp, Name: m.Name, - Resources: machine.ResourceConfig{ + Resources: vmconfigs.ResourceConfig{ CPUs: uint64(cfg.Hardware.CPUs), DiskSize: 0, Memory: cfg.Hardware.Memory, @@ -543,7 +545,7 @@ func (m *HyperVMachine) SSH(name string, opts machine.SSHOptions) error { if err != nil { return err } - if state != machine.Running { + if state != define.Running { return fmt.Errorf("vm %q is not running", m.Name) } @@ -614,21 +616,21 @@ func (m *HyperVMachine) Start(name string, opts machine.StartOptions) error { return m.writeConfig() } -func (m *HyperVMachine) State(_ bool) (machine.Status, error) { +func (m *HyperVMachine) State(_ bool) (define.Status, error) { vmm := hypervctl.NewVirtualMachineManager() vm, err := vmm.GetMachine(m.Name) if err != nil { return "", err } if vm.IsStarting() { - return machine.Starting, nil + return define.Starting, nil } if vm.State() == hypervctl.Enabled { - return machine.Running, nil + return define.Running, nil } // Following QEMU pattern here where only three // states seem valid - return machine.Stopped, nil + return define.Stopped, nil } func (m *HyperVMachine) Stop(name string, opts machine.StopOptions) error { @@ -911,19 +913,19 @@ func (m *HyperVMachine) createShares() (_ map[string]uint64, defErr error) { toReturn := make(map[string]uint64) for _, mount := range m.Mounts { - var vsock *HVSockRegistryEntry + var hvSock *vsock.HVSockRegistryEntry vsockNum, ok := m.MountVsocks[mount.Target] if ok { // Ignore errors here, we'll just try and recreate the // vsock below. - testVsock, err := LoadHVSockRegistryEntry(vsockNum) + testVsock, err := vsock.LoadHVSockRegistryEntry(vsockNum) if err == nil { - vsock = testVsock + hvSock = testVsock } } - if vsock == nil { - testVsock, err := NewHVSockRegistryEntry(m.Name, Fileserver) + if hvSock == nil { + testVsock, err := vsock.NewHVSockRegistryEntry(m.Name, vsock.Fileserver) if err != nil { return nil, err } @@ -934,12 +936,12 @@ func (m *HyperVMachine) createShares() (_ map[string]uint64, defErr error) { } } }() - vsock = testVsock + hvSock = testVsock } - logrus.Debugf("Going to share directory %s via 9p on vsock %d", mount.Source, vsock.Port) + logrus.Debugf("Going to share directory %s via 9p on vsock %d", mount.Source, hvSock.Port) - toReturn[mount.Target] = vsock.Port + toReturn[mount.Target] = hvSock.Port } return toReturn, nil @@ -955,7 +957,7 @@ func (m *HyperVMachine) removeShares() error { continue } - vsock, err := LoadHVSockRegistryEntry(vsockNum) + vsock, err := vsock.LoadHVSockRegistryEntry(vsockNum) if err != nil { logrus.Debugf("Vsock %d for mountpoint %s does not have a valid registry entry, skipping removal", vsockNum, mount.Target) continue diff --git a/pkg/machine/hyperv/vsock.go b/pkg/machine/hyperv/vsock/vsock.go similarity index 98% rename from pkg/machine/hyperv/vsock.go rename to pkg/machine/hyperv/vsock/vsock.go index 178694002f..f4789201bb 100644 --- a/pkg/machine/hyperv/vsock.go +++ b/pkg/machine/hyperv/vsock/vsock.go @@ -1,7 +1,7 @@ //go:build windows // +build windows -package hyperv +package vsock import ( "errors" @@ -9,8 +9,9 @@ import ( "net" "strings" + "github.com/containers/podman/v4/pkg/machine/sockets" + "github.com/Microsoft/go-winio" - "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/utils" "github.com/sirupsen/logrus" "golang.org/x/sys/windows/registry" @@ -274,7 +275,7 @@ func (hv *HVSockRegistryEntry) Listen() error { }() errChan := make(chan error) - go machine.ListenAndWaitOnSocket(errChan, listener) + go sockets.ListenAndWaitOnSocket(errChan, listener) return <-errChan } diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go index 9eaafe2faf..dc6ef05230 100644 --- a/pkg/machine/ignition.go +++ b/pkg/machine/ignition.go @@ -10,10 +10,7 @@ import ( "net/url" "os" "path/filepath" - "strings" - "github.com/containers/common/libnetwork/etchosts" - "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/pkg/machine/define" "github.com/sirupsen/logrus" ) @@ -28,7 +25,6 @@ import ( */ const ( - UserCertsTargetPath = "/etc/containers/certs.d" PodmanDockerTmpConfPath = "/etc/tmpfiles.d/podman-docker.conf" ) @@ -615,7 +611,7 @@ func prepareCertFile(path string, name string) (File, error) { return File{}, err } - targetPath := filepath.Join(UserCertsTargetPath, name) + targetPath := filepath.Join(define.UserCertsTargetPath, name) logrus.Debugf("Copying cert file from '%s' to '%s'.", path, targetPath) @@ -636,22 +632,6 @@ func prepareCertFile(path string, name string) (File, error) { return file, nil } -func GetProxyVariables() map[string]string { - proxyOpts := make(map[string]string) - for _, variable := range config.ProxyEnv { - if value, ok := os.LookupEnv(variable); ok { - if value == "" { - continue - } - - v := strings.ReplaceAll(value, "127.0.0.1", etchosts.HostContainersInternal) - v = strings.ReplaceAll(v, "localhost", etchosts.HostContainersInternal) - proxyOpts[variable] = v - } - } - return proxyOpts -} - func getLinks(usrName string) []Link { return []Link{{ Node: Node{ diff --git a/pkg/machine/qemu/command.go b/pkg/machine/qemu/command.go deleted file mode 100644 index c8dc315106..0000000000 --- a/pkg/machine/qemu/command.go +++ /dev/null @@ -1,106 +0,0 @@ -package qemu - -import ( - "fmt" - "strconv" - - "github.com/containers/podman/v4/pkg/machine" - "github.com/containers/podman/v4/pkg/machine/define" -) - -// QemuCmd is an alias around a string slice to prevent the need to migrate the -// MachineVM struct due to changes -type QemuCmd []string - -// NewQemuBuilder creates a new QemuCmd object that we will build on top of, -// starting with the qemu binary, architecture specific options, and propagated -// proxy and SSL settings -func NewQemuBuilder(binary string, options []string) QemuCmd { - q := QemuCmd{binary} - return append(q, options...) -} - -// SetMemory adds the specified amount of memory for the machine -func (q *QemuCmd) SetMemory(m uint64) { - *q = append(*q, "-m", strconv.FormatUint(m, 10)) -} - -// SetCPUs adds the number of CPUs the machine will have -func (q *QemuCmd) SetCPUs(c uint64) { - *q = append(*q, "-smp", strconv.FormatUint(c, 10)) -} - -// SetIgnitionFile specifies the machine's ignition file -func (q *QemuCmd) SetIgnitionFile(file define.VMFile) { - *q = append(*q, "-fw_cfg", "name=opt/com.coreos/config,file="+file.GetPath()) -} - -// SetQmpMonitor specifies the machine's qmp socket -func (q *QemuCmd) SetQmpMonitor(monitor Monitor) { - *q = append(*q, "-qmp", monitor.Network+":"+monitor.Address.GetPath()+",server=on,wait=off") -} - -// SetNetwork adds a network device to the machine -func (q *QemuCmd) SetNetwork() { - // Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is - // why we can only run one vm at a time right now - *q = append(*q, "-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee") -} - -// SetNetwork adds a network device to the machine -func (q *QemuCmd) SetUSBHostPassthrough(usbs []machine.USBConfig) { - if len(usbs) == 0 { - return - } - // Add xhci usb emulation first and then each usb device - *q = append(*q, "-device", "qemu-xhci") - for _, usb := range usbs { - var dev string - if usb.Bus != "" && usb.DevNumber != "" { - dev = fmt.Sprintf("usb-host,hostbus=%s,hostaddr=%s", usb.Bus, usb.DevNumber) - } else { - dev = fmt.Sprintf("usb-host,vendorid=%d,productid=%d", usb.Vendor, usb.Product) - } - *q = append(*q, "-device", dev) - } -} - -// SetSerialPort adds a serial port to the machine for readiness -func (q *QemuCmd) SetSerialPort(readySocket, vmPidFile define.VMFile, name string) { - *q = append(*q, - "-device", "virtio-serial", - // qemu needs to establish the long name; other connections can use the symlink'd - // Note both id and chardev start with an extra "a" because qemu requires that it - // starts with a letter but users can also use numbers - "-chardev", "socket,path="+readySocket.GetPath()+",server=on,wait=off,id=a"+name+"_ready", - "-device", "virtserialport,chardev=a"+name+"_ready"+",name=org.fedoraproject.port.0", - "-pidfile", vmPidFile.GetPath()) -} - -// SetVirtfsMount adds a virtfs mount to the machine -func (q *QemuCmd) SetVirtfsMount(source, tag, securityModel string, readonly bool) { - virtfsOptions := fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=%s", source, tag, securityModel) - if readonly { - virtfsOptions += ",readonly" - } - *q = append(*q, "-virtfs", virtfsOptions) -} - -// SetBootableImage specifies the image the machine will use to boot -func (q *QemuCmd) SetBootableImage(image string) { - *q = append(*q, "-drive", "if=virtio,file="+image) -} - -// SetDisplay specifies whether the machine will have a display -func (q *QemuCmd) SetDisplay(display string) { - *q = append(*q, "-display", display) -} - -// SetPropagatedHostEnvs adds options that propagate SSL and proxy settings -func (q *QemuCmd) SetPropagatedHostEnvs() { - *q = propagateHostEnv(*q) -} - -func (q *QemuCmd) Build() []string { - return *q -} diff --git a/pkg/machine/qemu/command/command.go b/pkg/machine/qemu/command/command.go new file mode 100644 index 0000000000..3619619ef3 --- /dev/null +++ b/pkg/machine/qemu/command/command.go @@ -0,0 +1,236 @@ +package command + +import ( + "encoding/base64" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/pkg/config" + "github.com/containers/podman/v4/pkg/machine/define" +) + +// QemuCmd is an alias around a string slice to prevent the need to migrate the +// MachineVM struct due to changes +type QemuCmd []string + +// NewQemuBuilder creates a new QemuCmd object that we will build on top of, +// starting with the qemu binary, architecture specific options, and propagated +// proxy and SSL settings +func NewQemuBuilder(binary string, options []string) QemuCmd { + q := QemuCmd{binary} + return append(q, options...) +} + +// SetMemory adds the specified amount of memory for the machine +func (q *QemuCmd) SetMemory(m uint64) { + *q = append(*q, "-m", strconv.FormatUint(m, 10)) +} + +// SetCPUs adds the number of CPUs the machine will have +func (q *QemuCmd) SetCPUs(c uint64) { + *q = append(*q, "-smp", strconv.FormatUint(c, 10)) +} + +// SetIgnitionFile specifies the machine's ignition file +func (q *QemuCmd) SetIgnitionFile(file define.VMFile) { + *q = append(*q, "-fw_cfg", "name=opt/com.coreos/config,file="+file.GetPath()) +} + +// SetQmpMonitor specifies the machine's qmp socket +func (q *QemuCmd) SetQmpMonitor(monitor Monitor) { + *q = append(*q, "-qmp", monitor.Network+":"+monitor.Address.GetPath()+",server=on,wait=off") +} + +// SetNetwork adds a network device to the machine +func (q *QemuCmd) SetNetwork() { + // Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is + // why we can only run one vm at a time right now + *q = append(*q, "-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee") +} + +// SetNetwork adds a network device to the machine +func (q *QemuCmd) SetUSBHostPassthrough(usbs []USBConfig) { + if len(usbs) == 0 { + return + } + // Add xhci usb emulation first and then each usb device + *q = append(*q, "-device", "qemu-xhci") + for _, usb := range usbs { + var dev string + if usb.Bus != "" && usb.DevNumber != "" { + dev = fmt.Sprintf("usb-host,hostbus=%s,hostaddr=%s", usb.Bus, usb.DevNumber) + } else { + dev = fmt.Sprintf("usb-host,vendorid=%d,productid=%d", usb.Vendor, usb.Product) + } + *q = append(*q, "-device", dev) + } +} + +// SetSerialPort adds a serial port to the machine for readiness +func (q *QemuCmd) SetSerialPort(readySocket, vmPidFile define.VMFile, name string) { + *q = append(*q, + "-device", "virtio-serial", + // qemu needs to establish the long name; other connections can use the symlink'd + // Note both id and chardev start with an extra "a" because qemu requires that it + // starts with a letter but users can also use numbers + "-chardev", "socket,path="+readySocket.GetPath()+",server=on,wait=off,id=a"+name+"_ready", + "-device", "virtserialport,chardev=a"+name+"_ready"+",name=org.fedoraproject.port.0", + "-pidfile", vmPidFile.GetPath()) +} + +// SetVirtfsMount adds a virtfs mount to the machine +func (q *QemuCmd) SetVirtfsMount(source, tag, securityModel string, readonly bool) { + virtfsOptions := fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=%s", source, tag, securityModel) + if readonly { + virtfsOptions += ",readonly" + } + *q = append(*q, "-virtfs", virtfsOptions) +} + +// SetBootableImage specifies the image the machine will use to boot +func (q *QemuCmd) SetBootableImage(image string) { + *q = append(*q, "-drive", "if=virtio,file="+image) +} + +// SetDisplay specifies whether the machine will have a display +func (q *QemuCmd) SetDisplay(display string) { + *q = append(*q, "-display", display) +} + +// SetPropagatedHostEnvs adds options that propagate SSL and proxy settings +func (q *QemuCmd) SetPropagatedHostEnvs() { + *q = propagateHostEnv(*q) +} + +func (q *QemuCmd) Build() []string { + return *q +} + +type USBConfig struct { + Bus string + DevNumber string + Vendor int + Product int +} + +func ParseUSBs(usbs []string) ([]USBConfig, error) { + configs := []USBConfig{} + for _, str := range usbs { + if str == "" { + // Ignore --usb="" as it can be used to reset USBConfigs + continue + } + + vals := strings.Split(str, ",") + if len(vals) != 2 { + return configs, fmt.Errorf("usb: fail to parse: missing ',': %s", str) + } + + left := strings.Split(vals[0], "=") + if len(left) != 2 { + return configs, fmt.Errorf("usb: fail to parse: missing '=': %s", str) + } + + right := strings.Split(vals[1], "=") + if len(right) != 2 { + return configs, fmt.Errorf("usb: fail to parse: missing '=': %s", str) + } + + option := left[0] + "_" + right[0] + + switch option { + case "bus_devnum", "devnum_bus": + bus, devnumber := left[1], right[1] + if right[0] == "bus" { + bus, devnumber = devnumber, bus + } + + configs = append(configs, USBConfig{ + Bus: bus, + DevNumber: devnumber, + }) + case "vendor_product", "product_vendor": + vendorStr, productStr := left[1], right[1] + if right[0] == "vendor" { + vendorStr, productStr = productStr, vendorStr + } + + vendor, err := strconv.ParseInt(vendorStr, 16, 0) + if err != nil { + return configs, fmt.Errorf("usb: fail to convert vendor of %s: %s", str, err) + } + + product, err := strconv.ParseInt(productStr, 16, 0) + if err != nil { + return configs, fmt.Errorf("usb: fail to convert product of %s: %s", str, err) + } + + configs = append(configs, USBConfig{ + Vendor: int(vendor), + Product: int(product), + }) + default: + return configs, fmt.Errorf("usb: fail to parse: %s", str) + } + } + return configs, nil +} + +func GetProxyVariables() map[string]string { + proxyOpts := make(map[string]string) + for _, variable := range config.ProxyEnv { + if value, ok := os.LookupEnv(variable); ok { + if value == "" { + continue + } + + v := strings.ReplaceAll(value, "127.0.0.1", etchosts.HostContainersInternal) + v = strings.ReplaceAll(v, "localhost", etchosts.HostContainersInternal) + proxyOpts[variable] = v + } + } + return proxyOpts +} + +// propagateHostEnv is here for providing the ability to propagate +// proxy and SSL settings (e.g. HTTP_PROXY and others) on a start +// and avoid a need of re-creating/re-initiating a VM +func propagateHostEnv(cmdLine QemuCmd) QemuCmd { + varsToPropagate := make([]string, 0) + + for k, v := range GetProxyVariables() { + varsToPropagate = append(varsToPropagate, fmt.Sprintf("%s=%q", k, v)) + } + + if sslCertFile, ok := os.LookupEnv("SSL_CERT_FILE"); ok { + pathInVM := filepath.Join(define.UserCertsTargetPath, filepath.Base(sslCertFile)) + varsToPropagate = append(varsToPropagate, fmt.Sprintf("%s=%q", "SSL_CERT_FILE", pathInVM)) + } + + if _, ok := os.LookupEnv("SSL_CERT_DIR"); ok { + varsToPropagate = append(varsToPropagate, fmt.Sprintf("%s=%q", "SSL_CERT_DIR", define.UserCertsTargetPath)) + } + + if len(varsToPropagate) > 0 { + prefix := "name=opt/com.coreos/environment,string=" + envVarsJoined := strings.Join(varsToPropagate, "|") + fwCfgArg := prefix + base64.StdEncoding.EncodeToString([]byte(envVarsJoined)) + return append(cmdLine, "-fw_cfg", fwCfgArg) + } + + return cmdLine +} + +type Monitor struct { + // Address portion of the qmp monitor (/tmp/tmp.sock) + Address define.VMFile + // Network portion of the qmp monitor (unix) + Network string + // Timeout in seconds for qmp monitor transactions + Timeout time.Duration +} diff --git a/pkg/machine/qemu/command/command_test.go b/pkg/machine/qemu/command/command_test.go new file mode 100644 index 0000000000..e2307f3693 --- /dev/null +++ b/pkg/machine/qemu/command/command_test.go @@ -0,0 +1,94 @@ +package command + +import ( + "encoding/base64" + "fmt" + "strings" + "testing" + + "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/podman/v4/pkg/machine/define" + "github.com/stretchr/testify/assert" +) + +func TestPropagateHostEnv(t *testing.T) { + tests := map[string]struct { + value string + expect string + }{ + "HTTP_PROXY": { + "proxy", + "equal", + }, + "ftp_proxy": { + "domain.com:8888", + "equal", + }, + "FTP_PROXY": { + "proxy", + "equal", + }, + "NO_PROXY": { + "localaddress", + "equal", + }, + "HTTPS_PROXY": { + "", + "unset", + }, + "no_proxy": { + "", + "unset", + }, + "http_proxy": { + "127.0.0.1:8888", + fmt.Sprintf("%s:8888", etchosts.HostContainersInternal), + }, + "https_proxy": { + "localhost:8888", + fmt.Sprintf("%s:8888", etchosts.HostContainersInternal), + }, + "SSL_CERT_FILE": { + "/some/f=oo.cert", + fmt.Sprintf("%s/f=oo.cert", define.UserCertsTargetPath), + }, + "SSL_CERT_DIR": { + "/some/my/certs", + define.UserCertsTargetPath, + }, + } + + for key, item := range tests { + t.Setenv(key, item.value) + } + + cmdLine := propagateHostEnv(make([]string, 0)) + + assert.Len(t, cmdLine, 2) + assert.Equal(t, "-fw_cfg", cmdLine[0]) + tokens := strings.Split(cmdLine[1], ",string=") + decodeString, err := base64.StdEncoding.DecodeString(tokens[1]) + assert.NoError(t, err) + + // envsRawArr looks like: ["BAR=\"bar\"", "FOO=\"foo\""] + envsRawArr := strings.Split(string(decodeString), "|") + // envs looks like: {"BAR": "bar", "FOO": "foo"} + envs := make(map[string]string) + for _, env := range envsRawArr { + item := strings.SplitN(env, "=", 2) + envs[item[0]] = strings.Trim(item[1], "\"") + } + + for key, test := range tests { + switch test.expect { + case "equal": + assert.Equal(t, envs[key], test.value) + case "unset": + if _, ok := envs[key]; ok { + t.Errorf("env %s should not be set", key) + } + default: + assert.Equal(t, envs[key], test.expect) + } + } +} diff --git a/pkg/machine/qemu/qemu_command_test.go b/pkg/machine/qemu/command/qemu_command_test.go similarity index 99% rename from pkg/machine/qemu/qemu_command_test.go rename to pkg/machine/qemu/command/qemu_command_test.go index 5041dcb156..ed198f2a0f 100644 --- a/pkg/machine/qemu/qemu_command_test.go +++ b/pkg/machine/qemu/command/qemu_command_test.go @@ -1,7 +1,7 @@ //go:build (amd64 && !windows) || (arm64 && !windows) // +build amd64,!windows arm64,!windows -package qemu +package command import ( "fmt" diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go index 25d5af5e7c..a47b77fe2b 100644 --- a/pkg/machine/qemu/config.go +++ b/pkg/machine/qemu/config.go @@ -6,7 +6,6 @@ import ( "io/fs" "os" "path/filepath" - "strconv" "strings" "time" @@ -14,6 +13,9 @@ import ( "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/compression" "github.com/containers/podman/v4/pkg/machine/define" + "github.com/containers/podman/v4/pkg/machine/qemu/command" + "github.com/containers/podman/v4/pkg/machine/sockets" + "github.com/containers/podman/v4/pkg/machine/vmconfigs" "github.com/containers/podman/v4/utils" "github.com/docker/go-units" "github.com/sirupsen/logrus" @@ -59,7 +61,7 @@ func (v *MachineVM) setQMPMonitorSocket() error { // setNewMachineCMD configure the CLI command that will be run to create the new // machine func (v *MachineVM) setNewMachineCMD(qemuBinary string, cmdOpts *setNewMachineCMDOpts) { - v.CmdLine = NewQemuBuilder(qemuBinary, v.addArchOptions(cmdOpts)) + v.CmdLine = command.NewQemuBuilder(qemuBinary, v.addArchOptions(cmdOpts)) v.CmdLine.SetMemory(v.Memory) v.CmdLine.SetCPUs(v.CPUs) v.CmdLine.SetIgnitionFile(v.IgnitionFile) @@ -69,69 +71,6 @@ func (v *MachineVM) setNewMachineCMD(qemuBinary string, cmdOpts *setNewMachineCM v.CmdLine.SetUSBHostPassthrough(v.USBs) } -func parseUSBs(usbs []string) ([]machine.USBConfig, error) { - configs := []machine.USBConfig{} - for _, str := range usbs { - if str == "" { - // Ignore --usb="" as it can be used to reset USBConfigs - continue - } - - vals := strings.Split(str, ",") - if len(vals) != 2 { - return configs, fmt.Errorf("usb: fail to parse: missing ',': %s", str) - } - - left := strings.Split(vals[0], "=") - if len(left) != 2 { - return configs, fmt.Errorf("usb: fail to parse: missing '=': %s", str) - } - - right := strings.Split(vals[1], "=") - if len(right) != 2 { - return configs, fmt.Errorf("usb: fail to parse: missing '=': %s", str) - } - - option := left[0] + "_" + right[0] - - switch option { - case "bus_devnum", "devnum_bus": - bus, devnumber := left[1], right[1] - if right[0] == "bus" { - bus, devnumber = devnumber, bus - } - - configs = append(configs, machine.USBConfig{ - Bus: bus, - DevNumber: devnumber, - }) - case "vendor_product", "product_vendor": - vendorStr, productStr := left[1], right[1] - if right[0] == "vendor" { - vendorStr, productStr = productStr, vendorStr - } - - vendor, err := strconv.ParseInt(vendorStr, 16, 0) - if err != nil { - return configs, fmt.Errorf("usb: fail to convert vendor of %s: %s", str, err) - } - - product, err := strconv.ParseInt(productStr, 16, 0) - if err != nil { - return configs, fmt.Errorf("usb: fail to convert product of %s: %s", str, err) - } - - configs = append(configs, machine.USBConfig{ - Vendor: int(vendor), - Product: int(product), - }) - default: - return configs, fmt.Errorf("usb: fail to parse: %s", str) - } - } - return configs, nil -} - // NewMachine initializes an instance of a virtual machine based on the qemu // virtualization. func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) { @@ -169,7 +108,7 @@ func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, e vm.CPUs = opts.CPUS vm.Memory = opts.Memory vm.DiskSize = opts.DiskSize - if vm.USBs, err = parseUSBs(opts.USBs); err != nil { + if vm.USBs, err = command.ParseUSBs(opts.USBs); err != nil { return nil, err } @@ -195,7 +134,7 @@ func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, e return nil, err } symlink := vm.Name + "_ready.sock" - if err := machine.SetSocket(&vm.ReadySocket, machine.ReadySocketPath(runtimeDir+"/podman/", vm.Name), &symlink); err != nil { + if err := sockets.SetSocket(&vm.ReadySocket, sockets.ReadySocketPath(runtimeDir+"/podman/", vm.Name), &symlink); err != nil { return nil, err } @@ -209,7 +148,7 @@ func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, e // and returns a vm instance func (p *QEMUVirtualization) LoadVMByName(name string) (machine.VM, error) { vm := &MachineVM{Name: name} - vm.HostUser = machine.HostUser{UID: -1} // posix reserves -1, so use it to signify undefined + vm.HostUser = vmconfigs.HostUser{UID: -1} // posix reserves -1, so use it to signify undefined if err := vm.update(); err != nil { return nil, err } @@ -274,7 +213,7 @@ func getVMInfos() ([]*machine.ListResponse, error) { if err != nil { return err } - listEntry.Running = state == machine.Running + listEntry.Running = state == define.Running listEntry.LastUp = vm.LastUp listed = append(listed, listEntry) diff --git a/pkg/machine/qemu/config_test.go b/pkg/machine/qemu/config_test.go index d1bd0f291e..ae630f355c 100644 --- a/pkg/machine/qemu/config_test.go +++ b/pkg/machine/qemu/config_test.go @@ -4,20 +4,20 @@ import ( "reflect" "testing" - "github.com/containers/podman/v4/pkg/machine" + "github.com/containers/podman/v4/pkg/machine/qemu/command" ) func TestUSBParsing(t *testing.T) { tests := []struct { name string args []string - result []machine.USBConfig + result []command.USBConfig wantErr bool }{ { name: "Good vendor and product", args: []string{"vendor=13d3,product=5406", "vendor=08ec,product=0016"}, - result: []machine.USBConfig{ + result: []command.USBConfig{ { Vendor: 5075, Product: 21510, @@ -32,7 +32,7 @@ func TestUSBParsing(t *testing.T) { { name: "Good bus and device number", args: []string{"bus=1,devnum=4", "bus=1,devnum=3"}, - result: []machine.USBConfig{ + result: []command.USBConfig{ { Bus: "1", DevNumber: "4", @@ -47,26 +47,26 @@ func TestUSBParsing(t *testing.T) { { name: "Bad vendor and product, not hexa", args: []string{"vendor=13dk,product=5406"}, - result: []machine.USBConfig{}, + result: []command.USBConfig{}, wantErr: true, }, { name: "Bad vendor and product, bad separator", args: []string{"vendor=13d3:product=5406"}, - result: []machine.USBConfig{}, + result: []command.USBConfig{}, wantErr: true, }, { name: "Bad vendor and product, missing equal", args: []string{"vendor=13d3:product-5406"}, - result: []machine.USBConfig{}, + result: []command.USBConfig{}, wantErr: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, err := parseUSBs(test.args) + got, err := command.ParseUSBs(test.args) if (err != nil) != test.wantErr { t.Errorf("parseUUBs error = %v, wantErr %v", err, test.wantErr) return diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index dac5b1a873..9a74113a44 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -6,7 +6,6 @@ package qemu import ( "bufio" "bytes" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -25,6 +24,9 @@ import ( gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/define" + "github.com/containers/podman/v4/pkg/machine/qemu/command" + "github.com/containers/podman/v4/pkg/machine/sockets" + "github.com/containers/podman/v4/pkg/machine/vmconfigs" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage/pkg/lockfile" @@ -66,13 +68,13 @@ type MachineVM struct { // ConfigPath is the path to the configuration file ConfigPath define.VMFile // The command line representation of the qemu command - CmdLine QemuCmd + CmdLine command.QemuCmd // HostUser contains info about host user - machine.HostUser + vmconfigs.HostUser // ImageConfig describes the bootable image machine.ImageConfig // Mounts is the list of remote filesystems to mount - Mounts []machine.Mount + Mounts []vmconfigs.Mount // Name of VM Name string // PidFilePath is the where the Proxy PID file lives @@ -80,13 +82,13 @@ type MachineVM struct { // VMPidFilePath is the where the VM PID file lives VMPidFilePath define.VMFile // QMPMonitor is the qemu monitor object for sending commands - QMPMonitor Monitor + QMPMonitor command.Monitor // ReadySocket tells host when vm is booted ReadySocket define.VMFile // ResourceConfig is physical attrs of the VM - machine.ResourceConfig + vmconfigs.ResourceConfig // SSHConfig for accessing the remote vm - machine.SSHConfig + vmconfigs.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 @@ -98,15 +100,6 @@ type MachineVM struct { lock *lockfile.LockFile } -type Monitor struct { - // Address portion of the qmp monitor (/tmp/tmp.sock) - Address define.VMFile - // Network portion of the qmp monitor (unix) - Network string - // Timeout in seconds for qmp monitor transactions - Timeout time.Duration -} - // addMountsToVM converts the volumes passed through the CLI into the specified // volume driver and adds them to the machine func (v *MachineVM) addMountsToVM(opts machine.InitOptions) error { @@ -119,7 +112,7 @@ func (v *MachineVM) addMountsToVM(opts machine.InitOptions) error { return fmt.Errorf("unknown volume driver: %s", opts.VolumeDriver) } - mounts := []machine.Mount{} + mounts := []vmconfigs.Mount{} for i, volume := range opts.Volumes { tag := fmt.Sprintf("vol%d", i) paths := pathsFromVolume(volume) @@ -128,7 +121,7 @@ func (v *MachineVM) addMountsToVM(opts machine.InitOptions) error { readonly, securityModel := extractMountOptions(paths) if volumeType == VolumeTypeVirtfs { v.CmdLine.SetVirtfsMount(source, tag, securityModel, readonly) - mounts = append(mounts, machine.Mount{Type: MountType9p, Tag: tag, Source: source, Target: target, ReadOnly: readonly}) + mounts = append(mounts, vmconfigs.Mount{Type: MountType9p, Tag: tag, Source: source, Target: target, ReadOnly: readonly}) } } v.Mounts = mounts @@ -274,7 +267,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { return setErrors, err } - if state == machine.Running { + if state == define.Running { suffix := "" if v.Name != machine.DefaultMachineName { suffix = " " + v.Name @@ -309,7 +302,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { } if opts.USBs != nil { - if usbConfigs, err := parseUSBs(*opts.USBs); err != nil { + if usbConfigs, err := command.ParseUSBs(*opts.USBs); err != nil { setErrors = append(setErrors, fmt.Errorf("failed to set usb: %w", err)) } else { v.USBs = usbConfigs @@ -381,7 +374,7 @@ func (v *MachineVM) conductVMReadinessCheck(name string, maxBackoffs int, backof if err != nil { return false, nil, err } - if state == machine.Running && v.isListening() { + if state == define.Running && v.isListening() { // Also make sure that SSH is up and running. The // ready service's dependencies don't fully make sure // that clients can SSH into the machine immediately @@ -469,9 +462,9 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error { return err } switch state { - case machine.Starting: + case define.Starting: return fmt.Errorf("cannot start VM %q: starting state indicates that a previous start has failed: please stop and restart the VM", v.Name) - case machine.Running: + case define.Running: return fmt.Errorf("cannot start VM %q: %w", v.Name, machine.ErrVMAlreadyRunning) } @@ -537,7 +530,7 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error { return err } - qemuSocketConn, err = machine.DialSocketWithBackoffs(maxBackoffs, defaultBackoff, v.QMPMonitor.Address.Path) + qemuSocketConn, err = sockets.DialSocketWithBackoffs(maxBackoffs, defaultBackoff, v.QMPMonitor.Address.Path) if err != nil { return err } @@ -592,7 +585,7 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error { fmt.Println("Waiting for VM ...") } - conn, err = machine.DialSocketWithBackoffsAndProcCheck(maxBackoffs, defaultBackoff, v.ReadySocket.GetPath(), checkProcessStatus, "qemu", cmd.Process.Pid, stderrBuf) + conn, err = sockets.DialSocketWithBackoffsAndProcCheck(maxBackoffs, defaultBackoff, v.ReadySocket.GetPath(), checkProcessStatus, "qemu", cmd.Process.Pid, stderrBuf) if err != nil { return err } @@ -656,36 +649,7 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error { return nil } -// propagateHostEnv is here for providing the ability to propagate -// proxy and SSL settings (e.g. HTTP_PROXY and others) on a start -// and avoid a need of re-creating/re-initiating a VM -func propagateHostEnv(cmdLine QemuCmd) QemuCmd { - varsToPropagate := make([]string, 0) - - for k, v := range machine.GetProxyVariables() { - varsToPropagate = append(varsToPropagate, fmt.Sprintf("%s=%q", k, v)) - } - - if sslCertFile, ok := os.LookupEnv("SSL_CERT_FILE"); ok { - pathInVM := filepath.Join(machine.UserCertsTargetPath, filepath.Base(sslCertFile)) - varsToPropagate = append(varsToPropagate, fmt.Sprintf("%s=%q", "SSL_CERT_FILE", pathInVM)) - } - - if _, ok := os.LookupEnv("SSL_CERT_DIR"); ok { - varsToPropagate = append(varsToPropagate, fmt.Sprintf("%s=%q", "SSL_CERT_DIR", machine.UserCertsTargetPath)) - } - - if len(varsToPropagate) > 0 { - prefix := "name=opt/com.coreos/environment,string=" - envVarsJoined := strings.Join(varsToPropagate, "|") - fwCfgArg := prefix + base64.StdEncoding.EncodeToString([]byte(envVarsJoined)) - return append(cmdLine, "-fw_cfg", fwCfgArg) - } - - return cmdLine -} - -func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, error) { +func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (define.Status, error) { // this is the format returned from the monitor // {"return": {"status": "running", "singlestep": false, "running": true}} @@ -712,17 +676,17 @@ func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, err b, err := monitor.Run(input) if err != nil { if errors.Is(err, os.ErrNotExist) { - return machine.Stopped, nil + return define.Stopped, nil } return "", err } if err := json.Unmarshal(b, &response); err != nil { return "", err } - if response.Response.Status == machine.Running { - return machine.Running, nil + if response.Response.Status == define.Running { + return define.Running, nil } - return machine.Stopped, nil + return define.Stopped, nil } // waitForMachineToStop waits for the machine to stop running @@ -734,7 +698,7 @@ func (v *MachineVM) waitForMachineToStop() error { if err != nil { return err } - if state != machine.Running { + if state != define.Running { break } time.Sleep(waitInternal) @@ -929,10 +893,10 @@ func (v *MachineVM) stopLocked() error { } // NewQMPMonitor creates the monitor subsection of our vm -func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) { +func NewQMPMonitor(network, name string, timeout time.Duration) (command.Monitor, error) { rtDir, err := getRuntimeDir() if err != nil { - return Monitor{}, err + return command.Monitor{}, err } if isRootful() { rtDir = "/run" @@ -940,7 +904,7 @@ func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) rtDir = filepath.Join(rtDir, "podman") if _, err := os.Stat(rtDir); errors.Is(err, fs.ErrNotExist) { if err := os.MkdirAll(rtDir, 0755); err != nil { - return Monitor{}, err + return command.Monitor{}, err } } if timeout == 0 { @@ -948,9 +912,9 @@ func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) } address, err := define.NewMachineFile(filepath.Join(rtDir, "qmp_"+name+".sock"), nil) if err != nil { - return Monitor{}, err + return command.Monitor{}, err } - monitor := Monitor{ + monitor := command.Monitor{ Network: network, Address: *address, Timeout: timeout, @@ -1021,7 +985,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() if err != nil { return "", nil, err } - if state == machine.Running { + if state == define.Running { if !opts.Force { return "", nil, &machine.ErrVMRunningCannotDestroyed{Name: v.Name} } @@ -1050,7 +1014,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() }, nil } -func (v *MachineVM) State(bypass bool) (machine.Status, error) { +func (v *MachineVM) State(bypass bool) (define.Status, error) { // Check if qmp socket path exists if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); errors.Is(err, fs.ErrNotExist) { return "", nil @@ -1061,7 +1025,7 @@ func (v *MachineVM) State(bypass bool) (machine.Status, error) { } // Check if we can dial it if v.Starting && !bypass { - return machine.Starting, nil + return define.Starting, nil } monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout) if err != nil { @@ -1069,7 +1033,7 @@ func (v *MachineVM) State(bypass bool) (machine.Status, error) { // it can appear as though the machine state is not stopped. Check for ECONNREFUSED // almost assures us that the vm is stopped. if errors.Is(err, syscall.ECONNREFUSED) { - return machine.Stopped, nil + return define.Stopped, nil } return "", err } @@ -1102,7 +1066,7 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { if err != nil { return err } - if state != machine.Running { + if state != define.Running { return fmt.Errorf("vm %q is not running", v.Name) } diff --git a/pkg/machine/qemu/machine_test.go b/pkg/machine/qemu/machine_test.go index dbf2ebaa68..354ea5b688 100644 --- a/pkg/machine/qemu/machine_test.go +++ b/pkg/machine/qemu/machine_test.go @@ -4,105 +4,18 @@ package qemu import ( - "encoding/base64" - "fmt" - "strings" "testing" - "github.com/containers/common/libnetwork/etchosts" - "github.com/containers/podman/v4/pkg/machine" - "github.com/stretchr/testify/assert" + "github.com/containers/podman/v4/pkg/machine/qemu/command" "github.com/stretchr/testify/require" ) func TestEditCmd(t *testing.T) { vm := new(MachineVM) - vm.CmdLine = QemuCmd{"command", "-flag", "value"} + vm.CmdLine = command.QemuCmd{"command", "-flag", "value"} vm.editCmdLine("-flag", "newvalue") vm.editCmdLine("-anotherflag", "anothervalue") require.Equal(t, vm.CmdLine.Build(), []string{"command", "-flag", "newvalue", "-anotherflag", "anothervalue"}) } - -func TestPropagateHostEnv(t *testing.T) { - tests := map[string]struct { - value string - expect string - }{ - "HTTP_PROXY": { - "proxy", - "equal", - }, - "ftp_proxy": { - "domain.com:8888", - "equal", - }, - "FTP_PROXY": { - "proxy", - "equal", - }, - "NO_PROXY": { - "localaddress", - "equal", - }, - "HTTPS_PROXY": { - "", - "unset", - }, - "no_proxy": { - "", - "unset", - }, - "http_proxy": { - "127.0.0.1:8888", - fmt.Sprintf("%s:8888", etchosts.HostContainersInternal), - }, - "https_proxy": { - "localhost:8888", - fmt.Sprintf("%s:8888", etchosts.HostContainersInternal), - }, - "SSL_CERT_FILE": { - "/some/f=oo.cert", - fmt.Sprintf("%s/f=oo.cert", machine.UserCertsTargetPath), - }, - "SSL_CERT_DIR": { - "/some/my/certs", - machine.UserCertsTargetPath, - }, - } - - for key, item := range tests { - t.Setenv(key, item.value) - } - - cmdLine := propagateHostEnv(make([]string, 0)) - - assert.Len(t, cmdLine, 2) - assert.Equal(t, "-fw_cfg", cmdLine[0]) - tokens := strings.Split(cmdLine[1], ",string=") - decodeString, err := base64.StdEncoding.DecodeString(tokens[1]) - assert.NoError(t, err) - - // envsRawArr looks like: ["BAR=\"bar\"", "FOO=\"foo\""] - envsRawArr := strings.Split(string(decodeString), "|") - // envs looks like: {"BAR": "bar", "FOO": "foo"} - envs := make(map[string]string) - for _, env := range envsRawArr { - item := strings.SplitN(env, "=", 2) - envs[item[0]] = strings.Trim(item[1], "\"") - } - - for key, test := range tests { - switch test.expect { - case "equal": - assert.Equal(t, envs[key], test.value) - case "unset": - if _, ok := envs[key]; ok { - t.Errorf("env %s should not be set", key) - } - default: - assert.Equal(t, envs[key], test.expect) - } - } -} diff --git a/pkg/machine/sockets.go b/pkg/machine/sockets/sockets.go similarity index 99% rename from pkg/machine/sockets.go rename to pkg/machine/sockets/sockets.go index a7d51061cb..6d966dbfb3 100644 --- a/pkg/machine/sockets.go +++ b/pkg/machine/sockets/sockets.go @@ -1,4 +1,4 @@ -package machine +package sockets import ( "bufio" diff --git a/pkg/machine/vmconfigs/config.go b/pkg/machine/vmconfigs/config.go new file mode 100644 index 0000000000..8a4cb1e718 --- /dev/null +++ b/pkg/machine/vmconfigs/config.go @@ -0,0 +1,142 @@ +package vmconfigs + +import ( + "errors" + "net/url" + "time" + + gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" + "github.com/containers/podman/v4/pkg/machine/define" + "github.com/containers/podman/v4/pkg/machine/qemu/command" + "github.com/containers/storage/pkg/lockfile" +) + +type aThing struct{} + +type MachineConfig struct { + // Common stuff + Created time.Time + GvProxy gvproxy.GvproxyCommand + HostUser HostUser + IgnitionFile *aThing // possible interface + LastUp time.Time + LogPath *define.VMFile `json:",omitempty"` // Revisit this for all providers + Mounts []Mount + Name string + ReadySocket *aThing // possible interface + Resources ResourceConfig + SSH SSHConfig + Starting *bool + Version uint + + // Image stuff + imageDescription machineImage //nolint:unused + + // Provider stuff + AppleHypervisor *AppleHVConfig `json:",omitempty"` + QEMUHypervisor *QEMUConfig `json:",omitempty"` + HyperVHypervisor *HyperVConfig `json:",omitempty"` + WSLHypervisor *WSLConfig `json:",omitempty"` + + lock *lockfile.LockFile //nolint:unused +} + +// MachineImage describes a podman machine image +type MachineImage struct { + OCI *ociMachineImage + FCOS *fcosMachineImage +} + +// Pull downloads a machine image +func (m *MachineImage) Pull() error { + if m.OCI != nil { + return m.OCI.download() + } + if m.FCOS != nil { + return m.FCOS.download() + } + return errors.New("no valid machine image provider detected") +} + +type machineImage interface { //nolint:unused + download() error + path() string +} + +type ociMachineImage struct { + // registry + // TODO JSON serial/deserial will write string to disk + // but in code it is a types.ImageReference + + // quay.io/podman/podman-machine-image:5.0 + FQImageReference string +} + +func (o ociMachineImage) path() string { + return "" +} + +func (o ociMachineImage) download() error { + return nil +} + +type fcosMachineImage struct { + // TODO JSON serial/deserial will write string to disk + // but in code is url.URL + Location url.URL // file://path/.qcow2 https://path/qcow2 +} + +func (f fcosMachineImage) download() error { + return nil +} + +func (f fcosMachineImage) path() string { + return "" +} + +// 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 + // Whether one of these fields has changed and actions should be taken + Modified bool `json:"HostUserModified"` +} + +type Mount struct { + ReadOnly bool + Source string + Tag string + Target string + Type string +} + +// 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 + // Usbs + USBs []command.USBConfig +} + +// 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 +} + +type VMStats struct { + // Created contains the original created time instead of querying the file mod time + Created time.Time + // LastUp contains the last recorded uptime + LastUp time.Time +} diff --git a/pkg/machine/vmconfigs/config_darwin.go b/pkg/machine/vmconfigs/config_darwin.go new file mode 100644 index 0000000000..62bdff414e --- /dev/null +++ b/pkg/machine/vmconfigs/config_darwin.go @@ -0,0 +1,15 @@ +package vmconfigs + +import ( + "github.com/containers/podman/v4/pkg/machine/applehv/vfkit" +) + +type AppleHVConfig struct { + // The VFKit endpoint where we can interact with the VM + Vfkit vfkit.VfkitHelper +} + +// Stubs +type HyperVConfig struct{} +type WSLConfig struct{} +type QEMUConfig struct{} diff --git a/pkg/machine/vmconfigs/config_freebsd.go b/pkg/machine/vmconfigs/config_freebsd.go new file mode 100644 index 0000000000..1970769ff4 --- /dev/null +++ b/pkg/machine/vmconfigs/config_freebsd.go @@ -0,0 +1,7 @@ +package vmconfigs + +// Stubs +type HyperVConfig struct{} +type WSLConfig struct {} +type QEMUConfig struct {} +type AppleHVConfig struct {} diff --git a/pkg/machine/vmconfigs/config_linux.go b/pkg/machine/vmconfigs/config_linux.go new file mode 100644 index 0000000000..59d37e8f44 --- /dev/null +++ b/pkg/machine/vmconfigs/config_linux.go @@ -0,0 +1,14 @@ +package vmconfigs + +import ( + "github.com/containers/podman/v4/pkg/machine/qemu/command" +) + +type QEMUConfig struct { + cmd command.QemuCmd //nolint:unused +} + +// Stubs +type AppleHVConfig struct{} +type HyperVConfig struct{} +type WSLConfig struct{} diff --git a/pkg/machine/vmconfigs/config_windows.go b/pkg/machine/vmconfigs/config_windows.go new file mode 100644 index 0000000000..8cec6976ea --- /dev/null +++ b/pkg/machine/vmconfigs/config_windows.go @@ -0,0 +1,21 @@ +package vmconfigs + +import ( + "github.com/containers/podman/v4/pkg/machine/hyperv/vsock" +) + +type HyperVConfig struct { + // NetworkVSock is for the user networking + NetworkHVSock vsock.HVSockRegistryEntry + // MountVsocks contains the currently-active vsocks, mapped to the + // directory they should be mounted on. + MountVsocks map[string]uint64 +} + +type WSLConfig struct { + wslstuff *aThing +} + +// Stubs +type QEMUConfig struct{} +type AppleHVConfig struct{} diff --git a/pkg/machine/volumes.go b/pkg/machine/volumes.go index 5f7eaf07de..b2a9d9de41 100644 --- a/pkg/machine/volumes.go +++ b/pkg/machine/volumes.go @@ -2,6 +2,8 @@ package machine import ( "strings" + + "github.com/containers/podman/v4/pkg/machine/vmconfigs" ) type Volume interface { @@ -37,8 +39,8 @@ func (v VirtIoFs) unitName() string { return unit } -func (v VirtIoFs) ToMount() Mount { - return Mount{ +func (v VirtIoFs) ToMount() vmconfigs.Mount { + return vmconfigs.Mount{ ReadOnly: v.ReadOnly, Source: v.Source, Tag: v.Tag, diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index e97baf51cb..10bf00e794 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -20,6 +20,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/define" + "github.com/containers/podman/v4/pkg/machine/vmconfigs" "github.com/containers/podman/v4/pkg/machine/wsl/wutil" "github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/utils" @@ -298,7 +299,7 @@ type MachineVM struct { // Whether this machine should run in a rootful or rootless manner Rootful bool // SSH identity, username, etc - machine.SSHConfig + vmconfigs.SSHConfig // machine version Version int // Whether to use user-mode networking @@ -1526,12 +1527,12 @@ func unregisterDist(dist string) error { return cmd.Run() } -func (v *MachineVM) State(bypass bool) (machine.Status, error) { +func (v *MachineVM) State(bypass bool) (define.Status, error) { if v.isRunning() { - return machine.Running, nil + return define.Running, nil } - return machine.Stopped, nil + return define.Stopped, nil } func stopWinProxy(v *MachineVM) error { @@ -1808,7 +1809,7 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { machinePipe := toDist(v.Name) connInfo.PodmanPipe = &define.VMFile{Path: `\\.\pipe\` + machinePipe} - created, lastUp, _ := v.updateTimeStamps(state == machine.Running) + created, lastUp, _ := v.updateTimeStamps(state == define.Running) return &machine.InspectInfo{ ConfigPath: define.VMFile{Path: v.ConfigPath}, ConnectionInfo: *connInfo, @@ -1827,7 +1828,7 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { }, nil } -func (v *MachineVM) getResources() (resources machine.ResourceConfig) { +func (v *MachineVM) getResources() (resources vmconfigs.ResourceConfig) { resources.CPUs, _ = getCPUs(v) resources.Memory, _ = getMem(v) resources.DiskSize = getDiskSize(v)