Re-organize hypervisor implementations

Ensures that for each hypervisor implementation, their `config.go` file
deals with implementing the `VirtProvider` interface while the
`machine.go` file is for implementing the `VM` interface.

Moves the `Virtualization` type into a common file and
created wrappers for the individual hypervisors. Allows for shared
functions that are exactly the same while providing the flexibility to
create hypervisor-specific implementations of the functions.

[NO NEW TESTS NEEDED]

Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
This commit is contained in:
Jake Correnti 2023-06-22 17:42:08 -04:00
parent 49e0bde2bf
commit 516034215f
13 changed files with 627 additions and 643 deletions

View File

@ -29,7 +29,7 @@ func GetSystemProvider() (machine.VirtProvider, error) {
logrus.Debugf("Using Podman machine with `%s` virtualization provider", resolvedVMType.String())
switch resolvedVMType {
case machine.QemuVirt:
return qemu.GetVirtualizationProvider(), nil
return qemu.VirtualizationProvider(), nil
default:
return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String())
}

View File

@ -30,9 +30,9 @@ func GetSystemProvider() (machine.VirtProvider, error) {
logrus.Debugf("Using Podman machine with `%s` virtualization provider", resolvedVMType.String())
switch resolvedVMType {
case machine.QemuVirt:
return qemu.GetVirtualizationProvider(), nil
return qemu.VirtualizationProvider(), nil
case machine.AppleHvVirt:
return applehv.GetVirtualizationProvider(), nil
return applehv.VirtualizationProvider(), nil
default:
return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String())
}

View File

@ -28,9 +28,9 @@ func GetSystemProvider() (machine.VirtProvider, error) {
logrus.Debugf("Using Podman machine with `%s` virtualization provider", resolvedVMType.String())
switch resolvedVMType {
case machine.WSLVirt:
return wsl.GetWSLProvider(), nil
return wsl.VirtualizationProvider(), nil
case machine.HyperVVirt:
return hyperv.GetVirtualizationProvider(), nil
return hyperv.VirtualizationProvider(), nil
default:
return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String())
}

View File

@ -17,10 +17,8 @@ const (
defaultVFKitEndpoint = "http://localhost:8081"
)
type Virtualization struct {
artifact machine.Artifact
compression machine.ImageCompression
format machine.ImageFormat
type AppleHVVirtualization struct {
machine.Virtualization
}
type MMHardwareConfig struct {
@ -30,11 +28,13 @@ type MMHardwareConfig struct {
Memory int32
}
func (v Virtualization) Artifact() machine.Artifact {
return machine.Metal
func VirtualizationProvider() machine.VirtProvider {
return &AppleHVVirtualization{
machine.NewVirtualization(machine.Metal, machine.Xz, machine.Raw),
}
}
func (v Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
func (v AppleHVVirtualization) CheckExclusiveActiveVM() (bool, string, error) {
fsVms, err := getVMInfos()
if err != nil {
return false, "", err
@ -48,15 +48,7 @@ func (v Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
return false, "", nil
}
func (v Virtualization) Compression() machine.ImageCompression {
return v.compression
}
func (v Virtualization) Format() machine.ImageFormat {
return v.format
}
func (v Virtualization) IsValidVMName(name string) (bool, error) {
func (v AppleHVVirtualization) IsValidVMName(name string) (bool, error) {
mm := MacMachine{Name: name}
configDir, err := machine.GetConfDir(machine.AppleHvVirt)
if err != nil {
@ -68,7 +60,7 @@ func (v Virtualization) IsValidVMName(name string) (bool, error) {
return true, nil
}
func (v Virtualization) List(opts machine.ListOptions) ([]*machine.ListResponse, error) {
func (v AppleHVVirtualization) List(opts machine.ListOptions) ([]*machine.ListResponse, error) {
var (
response []*machine.ListResponse
)
@ -108,12 +100,12 @@ func (v Virtualization) List(opts machine.ListOptions) ([]*machine.ListResponse,
return response, nil
}
func (v Virtualization) LoadVMByName(name string) (machine.VM, error) {
func (v AppleHVVirtualization) LoadVMByName(name string) (machine.VM, error) {
m := MacMachine{Name: name}
return m.loadFromFile()
}
func (v Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
func (v AppleHVVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
m := MacMachine{Name: opts.Name}
configDir, err := machine.GetConfDir(machine.AppleHvVirt)
@ -149,16 +141,16 @@ func (v Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error)
return m.loadFromFile()
}
func (v Virtualization) RemoveAndCleanMachines() error {
func (v AppleHVVirtualization) RemoveAndCleanMachines() error {
// This can be implemented when host networking is completed.
return machine.ErrNotImplemented
}
func (v Virtualization) VMType() machine.VMType {
func (v AppleHVVirtualization) VMType() machine.VMType {
return vmtype
}
func (v Virtualization) loadFromLocalJson() ([]*MacMachine, error) {
func (v AppleHVVirtualization) loadFromLocalJson() ([]*MacMachine, error) {
var (
jsonFiles []string
mms []*MacMachine

View File

@ -27,14 +27,6 @@ var (
vmtype = machine.AppleHvVirt
)
func GetVirtualizationProvider() machine.VirtProvider {
return &Virtualization{
artifact: machine.None,
compression: machine.Xz,
format: machine.Raw,
}
}
// VfkitHelper describes the use of vfkit: cmdline and endpoint
type VfkitHelper struct {
Bootloader string

View File

@ -48,19 +48,6 @@ const (
DefaultMachineName string = "podman-machine-default"
)
type VirtProvider interface {
Artifact() Artifact
CheckExclusiveActiveVM() (bool, string, error)
Compression() ImageCompression
Format() ImageFormat
IsValidVMName(name string) (bool, error)
List(opts ListOptions) ([]*ListResponse, error)
LoadVMByName(name string) (VM, error)
NewMachine(opts InitOptions) (VM, error)
RemoveAndCleanMachines() error
VMType() VMType
}
type RemoteConnectionType string
var (
@ -428,3 +415,42 @@ func ParseVMType(input string, emptyFallback VMType) (VMType, error) {
return QemuVirt, fmt.Errorf("unknown VMType `%s`", input)
}
}
type VirtProvider interface {
Artifact() Artifact
CheckExclusiveActiveVM() (bool, string, error)
Compression() ImageCompression
Format() ImageFormat
IsValidVMName(name string) (bool, error)
List(opts ListOptions) ([]*ListResponse, error)
LoadVMByName(name string) (VM, error)
NewMachine(opts InitOptions) (VM, error)
RemoveAndCleanMachines() error
VMType() VMType
}
type Virtualization struct {
artifact Artifact
compression ImageCompression
format ImageFormat
}
func (p *Virtualization) Artifact() Artifact {
return p.artifact
}
func (p *Virtualization) Compression() ImageCompression {
return p.compression
}
func (p *Virtualization) Format() ImageFormat {
return p.format
}
func NewVirtualization(artifact Artifact, compression ImageCompression, format ImageFormat) Virtualization {
return Virtualization{
artifact,
compression,
format,
}
}

View File

@ -44,7 +44,7 @@ func TestMachine(t *testing.T) {
}
var _ = BeforeSuite(func() {
qemuVP := qemu.GetVirtualizationProvider()
qemuVP := qemu.VirtualizationProvider()
fcd, err := machine.GetFCOSDownload(qemuVP, defaultStream)
if err != nil {
Fail("unable to get virtual machine image")

View File

@ -17,17 +17,17 @@ import (
"github.com/sirupsen/logrus"
)
type Virtualization struct {
artifact machine.Artifact
compression machine.ImageCompression
format machine.ImageFormat
type HyperVVirtualization struct {
machine.Virtualization
}
func (v Virtualization) Artifact() machine.Artifact {
return machine.None
func VirtualizationProvider() machine.VirtProvider {
return &HyperVVirtualization{
machine.NewVirtualization(machine.HyperV, machine.Zip, machine.Vhdx),
}
}
func (v Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
func (v HyperVVirtualization) CheckExclusiveActiveVM() (bool, string, error) {
vmm := hypervctl.NewVirtualMachineManager()
// Use of GetAll is OK here because we do not want to use the same name
// as something already *actually* configured in hyperv
@ -43,15 +43,7 @@ func (v Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
return false, "", nil
}
func (v Virtualization) Compression() machine.ImageCompression {
return v.compression
}
func (v Virtualization) Format() machine.ImageFormat {
return v.format
}
func (v Virtualization) IsValidVMName(name string) (bool, error) {
func (v HyperVVirtualization) IsValidVMName(name string) (bool, error) {
// We check both the local filesystem and hyperv for the valid name
mm := HyperVMachine{Name: name}
configDir, err := machine.GetConfDir(v.VMType())
@ -69,7 +61,7 @@ func (v Virtualization) IsValidVMName(name string) (bool, error) {
return true, nil
}
func (v Virtualization) List(opts machine.ListOptions) ([]*machine.ListResponse, error) {
func (v HyperVVirtualization) List(opts machine.ListOptions) ([]*machine.ListResponse, error) {
mms, err := v.loadFromLocalJson()
if err != nil {
return nil, err
@ -103,12 +95,12 @@ func (v Virtualization) List(opts machine.ListOptions) ([]*machine.ListResponse,
return response, err
}
func (v Virtualization) LoadVMByName(name string) (machine.VM, error) {
func (v HyperVVirtualization) LoadVMByName(name string) (machine.VM, error) {
m := &HyperVMachine{Name: name}
return m.loadFromFile()
}
func (v Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
func (v HyperVVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
m := HyperVMachine{Name: opts.Name}
if len(opts.ImagePath) < 1 {
return nil, errors.New("must define --image-path for hyperv support")
@ -180,7 +172,7 @@ func (v Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error)
return v.LoadVMByName(opts.Name)
}
func (v Virtualization) RemoveAndCleanMachines() error {
func (v HyperVVirtualization) RemoveAndCleanMachines() error {
// Error handling used here is following what qemu did
var (
prevErr error
@ -238,11 +230,11 @@ func (v Virtualization) RemoveAndCleanMachines() error {
return prevErr
}
func (v Virtualization) VMType() machine.VMType {
func (v HyperVVirtualization) VMType() machine.VMType {
return vmtype
}
func (v Virtualization) loadFromLocalJson() ([]*HyperVMachine, error) {
func (v HyperVVirtualization) loadFromLocalJson() ([]*HyperVMachine, error) {
var (
jsonFiles []string
mms []*HyperVMachine

View File

@ -30,14 +30,6 @@ var (
vmtype = machine.HyperVVirt
)
func GetVirtualizationProvider() machine.VirtProvider {
return &Virtualization{
artifact: machine.HyperV,
compression: machine.Zip,
format: machine.Vhdx,
}
}
const (
// Some of this will need to change when we are closer to having
// working code.

View File

@ -1,15 +1,312 @@
package qemu
import (
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/utils"
"github.com/docker/go-units"
"github.com/sirupsen/logrus"
)
type Virtualization struct {
artifact machine.Artifact
compression machine.ImageCompression
format machine.ImageFormat
type QEMUVirtualization struct {
machine.Virtualization
}
// NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization.
func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
}
vm := new(MachineVM)
if len(opts.Name) > 0 {
vm.Name = opts.Name
}
ignitionFile, err := machine.NewMachineFile(filepath.Join(vmConfigDir, vm.Name+".ign"), nil)
if err != nil {
return nil, err
}
vm.IgnitionFile = *ignitionFile
imagePath, err := machine.NewMachineFile(opts.ImagePath, nil)
if err != nil {
return nil, err
}
vm.ImagePath = *imagePath
vm.RemoteUsername = opts.Username
// Add a random port for ssh
port, err := utils.GetRandomPort()
if err != nil {
return nil, err
}
vm.Port = port
vm.CPUs = opts.CPUS
vm.Memory = opts.Memory
vm.DiskSize = opts.DiskSize
vm.Created = time.Now()
// Find the qemu executable
cfg, err := config.Default()
if err != nil {
return nil, err
}
execPath, err := cfg.FindHelperBinary(QemuCommand, true)
if err != nil {
return nil, err
}
if err := vm.setPIDSocket(); err != nil {
return nil, err
}
cmd := []string{execPath}
// Add memory
cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...)
// Add cpus
cmd = append(cmd, []string{"-smp", strconv.Itoa(int(vm.CPUs))}...)
// Add ignition file
cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFile.GetPath()}...)
// Add qmp socket
monitor, err := NewQMPMonitor("unix", vm.Name, defaultQMPTimeout)
if err != nil {
return nil, err
}
vm.QMPMonitor = monitor
cmd = append(cmd, []string{"-qmp", monitor.Network + ":" + monitor.Address.GetPath() + ",server=on,wait=off"}...)
// Add network
// Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is
// why we can only run one vm at a time right now
cmd = append(cmd, []string{"-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee"}...)
if err := vm.setReadySocket(); err != nil {
return nil, err
}
// Add serial port for readiness
cmd = append(cmd, []string{
"-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=" + vm.ReadySocket.Path + ",server=on,wait=off,id=a" + vm.Name + "_ready",
"-device", "virtserialport,chardev=a" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0",
"-pidfile", vm.VMPidFilePath.GetPath()}...)
vm.CmdLine = cmd
return vm, nil
}
// LoadVMByName reads a json file that describes a known qemu vm
// 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
if err := vm.update(); err != nil {
return nil, err
}
return vm, nil
}
// List lists all vm's that use qemu virtualization
func (p *QEMUVirtualization) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
return getVMInfos()
}
func getVMInfos() ([]*machine.ListResponse, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
}
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 := os.ReadFile(fullPath)
if err != nil {
return err
}
err = json.Unmarshal(b, vm)
if err != nil {
// Checking if the file did not unmarshal because it is using
// the deprecated config file format.
migrateErr := migrateVM(fullPath, b, vm)
if migrateErr != nil {
return migrateErr
}
}
listEntry := new(machine.ListResponse)
listEntry.Name = vm.Name
listEntry.Stream = vm.ImageStream
listEntry.VMType = "qemu"
listEntry.CPUs = vm.CPUs
listEntry.Memory = vm.Memory * units.MiB
listEntry.DiskSize = vm.DiskSize * units.GiB
listEntry.Port = vm.Port
listEntry.RemoteUsername = vm.RemoteUsername
listEntry.IdentityPath = vm.IdentityPath
listEntry.CreatedAt = vm.Created
listEntry.Starting = vm.Starting
listEntry.UserModeNetworking = true // always true
if listEntry.CreatedAt.IsZero() {
listEntry.CreatedAt = time.Now()
vm.Created = time.Now()
if err := vm.writeConfig(); err != nil {
return err
}
}
state, err := vm.State(false)
if err != nil {
return err
}
listEntry.Running = state == machine.Running
if !vm.LastUp.IsZero() { // this means we have already written a time to the config
listEntry.LastUp = vm.LastUp
} else { // else we just created the machine AKA last up = created time
listEntry.LastUp = vm.Created
vm.LastUp = listEntry.LastUp
if err := vm.writeConfig(); err != nil {
return err
}
}
listed = append(listed, listEntry)
}
return nil
}); err != nil {
return nil, err
}
return listed, err
}
func (p *QEMUVirtualization) IsValidVMName(name string) (bool, error) {
infos, err := getVMInfos()
if err != nil {
return false, err
}
for _, vm := range infos {
if vm.Name == name {
return true, nil
}
}
return false, nil
}
// CheckExclusiveActiveVM checks if there is a VM already running
// that does not allow other VMs to be running
func (p *QEMUVirtualization) CheckExclusiveActiveVM() (bool, string, error) {
vms, err := getVMInfos()
if err != nil {
return false, "", fmt.Errorf("checking VM active: %w", err)
}
for _, vm := range vms {
if vm.Running || vm.Starting {
return true, vm.Name, nil
}
}
return false, "", nil
}
// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
func (p *QEMUVirtualization) 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 := machine.GuardedRemoveAll(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 := machine.GuardedRemoveAll(confDir)
if err != nil {
if prevErr != nil {
logrus.Error(prevErr)
}
prevErr = err
}
}
return prevErr
}
func (p *QEMUVirtualization) VMType() machine.VMType {
return vmtype
}
func VirtualizationProvider() machine.VirtProvider {
return &QEMUVirtualization{
machine.NewVirtualization(machine.Qemu, machine.Xz, machine.Qcow),
}
}
// Deprecated: MachineVMV1 is being deprecated in favor a more flexible and informative
@ -47,39 +344,6 @@ type MachineVMV1 struct {
UID int
}
type MachineVM struct {
// ConfigPath is the path to the configuration file
ConfigPath machine.VMFile
// The command line representation of the qemu command
CmdLine []string
// HostUser contains info about host user
machine.HostUser
// ImageConfig describes the bootable image
machine.ImageConfig
// Mounts is the list of remote filesystems to mount
Mounts []machine.Mount
// Name of VM
Name string
// PidFilePath is the where the Proxy PID file lives
PidFilePath machine.VMFile
// VMPidFilePath is the where the VM PID file lives
VMPidFilePath machine.VMFile
// QMPMonitor is the qemu monitor object for sending commands
QMPMonitor Monitor
// ReadySocket tells host when vm is booted
ReadySocket machine.VMFile
// ResourceConfig is physical attrs of the VM
machine.ResourceConfig
// SSHConfig for accessing the remote vm
machine.SSHConfig
// Starting tells us whether the machine is running or if we have just dialed it to start it
Starting bool
// Created contains the original created time instead of querying the file mod time
Created time.Time
// LastUp contains the last recorded uptime
LastUp time.Time
}
type Monitorv1 struct {
// Address portion of the qmp monitor (/tmp/tmp.sock)
Address string
@ -89,15 +353,6 @@ type Monitorv1 struct {
Timeout time.Duration
}
type Monitor struct {
// Address portion of the qmp monitor (/tmp/tmp.sock)
Address machine.VMFile
// Network portion of the qmp monitor (unix)
Network string
// Timeout in seconds for qmp monitor transactions
Timeout time.Duration
}
var (
// defaultQMPTimeout is the timeout duration for the
// qmp monitor interactions.

View File

@ -28,10 +28,8 @@ import (
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/podman/v4/utils"
"github.com/containers/storage/pkg/ioutils"
"github.com/digitalocean/go-qemu/qmp"
"github.com/docker/go-units"
"github.com/sirupsen/logrus"
)
@ -41,14 +39,6 @@ var (
vmtype = machine.QemuVirt
)
func GetVirtualizationProvider() machine.VirtProvider {
return &Virtualization{
artifact: machine.Qemu,
compression: machine.Xz,
format: machine.Qcow,
}
}
const (
VolumeTypeVirtfs = "virtfs"
MountType9p = "9p"
@ -57,88 +47,46 @@ const (
apiUpTimeout = 20 * time.Second
)
// NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization.
func (p *Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
}
vm := new(MachineVM)
if len(opts.Name) > 0 {
vm.Name = opts.Name
}
ignitionFile, err := machine.NewMachineFile(filepath.Join(vmConfigDir, vm.Name+".ign"), nil)
if err != nil {
return nil, err
}
vm.IgnitionFile = *ignitionFile
imagePath, err := machine.NewMachineFile(opts.ImagePath, nil)
if err != nil {
return nil, err
}
vm.ImagePath = *imagePath
vm.RemoteUsername = opts.Username
type MachineVM struct {
// ConfigPath is the path to the configuration file
ConfigPath machine.VMFile
// The command line representation of the qemu command
CmdLine []string
// HostUser contains info about host user
machine.HostUser
// ImageConfig describes the bootable image
machine.ImageConfig
// Mounts is the list of remote filesystems to mount
Mounts []machine.Mount
// Name of VM
Name string
// PidFilePath is the where the Proxy PID file lives
PidFilePath machine.VMFile
// VMPidFilePath is the where the VM PID file lives
VMPidFilePath machine.VMFile
// QMPMonitor is the qemu monitor object for sending commands
QMPMonitor Monitor
// ReadySocket tells host when vm is booted
ReadySocket machine.VMFile
// ResourceConfig is physical attrs of the VM
machine.ResourceConfig
// SSHConfig for accessing the remote vm
machine.SSHConfig
// Starting tells us whether the machine is running or if we have just dialed it to start it
Starting bool
// Created contains the original created time instead of querying the file mod time
Created time.Time
// LastUp contains the last recorded uptime
LastUp time.Time
}
// Add a random port for ssh
port, err := utils.GetRandomPort()
if err != nil {
return nil, err
}
vm.Port = port
vm.CPUs = opts.CPUS
vm.Memory = opts.Memory
vm.DiskSize = opts.DiskSize
vm.Created = time.Now()
// Find the qemu executable
cfg, err := config.Default()
if err != nil {
return nil, err
}
execPath, err := cfg.FindHelperBinary(QemuCommand, true)
if err != nil {
return nil, err
}
if err := vm.setPIDSocket(); err != nil {
return nil, err
}
cmd := []string{execPath}
// Add memory
cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...)
// Add cpus
cmd = append(cmd, []string{"-smp", strconv.Itoa(int(vm.CPUs))}...)
// Add ignition file
cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFile.GetPath()}...)
// Add qmp socket
monitor, err := NewQMPMonitor("unix", vm.Name, defaultQMPTimeout)
if err != nil {
return nil, err
}
vm.QMPMonitor = monitor
cmd = append(cmd, []string{"-qmp", monitor.Network + ":" + monitor.Address.GetPath() + ",server=on,wait=off"}...)
// Add network
// Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is
// why we can only run one vm at a time right now
cmd = append(cmd, []string{"-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee"}...)
if err := vm.setReadySocket(); err != nil {
return nil, err
}
// Add serial port for readiness
cmd = append(cmd, []string{
"-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=" + vm.ReadySocket.Path + ",server=on,wait=off,id=a" + vm.Name + "_ready",
"-device", "virtserialport,chardev=a" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0",
"-pidfile", vm.VMPidFilePath.GetPath()}...)
vm.CmdLine = cmd
return vm, nil
type Monitor struct {
// Address portion of the qmp monitor (/tmp/tmp.sock)
Address machine.VMFile
// Network portion of the qmp monitor (unix)
Network string
// Timeout in seconds for qmp monitor transactions
Timeout time.Duration
}
// migrateVM takes the old configuration structure and migrates it
@ -224,18 +172,6 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error {
return os.Remove(configPath + ".orig")
}
// LoadVMByName reads a json file that describes a known qemu vm
// and returns a vm instance
func (p *Virtualization) 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
if err := vm.update(); err != nil {
return nil, err
}
return vm, nil
}
// Init writes the json configuration file to the filesystem for
// other verbs (start, stop)
func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
@ -250,7 +186,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
case machine.Testing.String(), machine.Next.String(), machine.Stable.String(), "":
// Get image as usual
v.ImageStream = opts.ImagePath
vp := GetVirtualizationProvider()
vp := VirtualizationProvider()
dd, err := machine.NewFcosDownloader(vmtype, v.Name, machine.FCOSStreamFromString(opts.ImagePath), vp)
if err != nil {
@ -1118,124 +1054,6 @@ func getDiskSize(path string) (uint64, error) {
return tmpInfo.VirtualSize, nil
}
// List lists all vm's that use qemu virtualization
func (p *Virtualization) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
return getVMInfos()
}
func getVMInfos() ([]*machine.ListResponse, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
}
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 := os.ReadFile(fullPath)
if err != nil {
return err
}
err = json.Unmarshal(b, vm)
if err != nil {
// Checking if the file did not unmarshal because it is using
// the deprecated config file format.
migrateErr := migrateVM(fullPath, b, vm)
if migrateErr != nil {
return migrateErr
}
}
listEntry := new(machine.ListResponse)
listEntry.Name = vm.Name
listEntry.Stream = vm.ImageStream
listEntry.VMType = "qemu"
listEntry.CPUs = vm.CPUs
listEntry.Memory = vm.Memory * units.MiB
listEntry.DiskSize = vm.DiskSize * units.GiB
listEntry.Port = vm.Port
listEntry.RemoteUsername = vm.RemoteUsername
listEntry.IdentityPath = vm.IdentityPath
listEntry.CreatedAt = vm.Created
listEntry.Starting = vm.Starting
listEntry.UserModeNetworking = true // always true
if listEntry.CreatedAt.IsZero() {
listEntry.CreatedAt = time.Now()
vm.Created = time.Now()
if err := vm.writeConfig(); err != nil {
return err
}
}
state, err := vm.State(false)
if err != nil {
return err
}
listEntry.Running = state == machine.Running
if !vm.LastUp.IsZero() { // this means we have already written a time to the config
listEntry.LastUp = vm.LastUp
} else { // else we just created the machine AKA last up = created time
listEntry.LastUp = vm.Created
vm.LastUp = listEntry.LastUp
if err := vm.writeConfig(); err != nil {
return err
}
}
listed = append(listed, listEntry)
}
return nil
}); err != nil {
return nil, err
}
return listed, err
}
func (p *Virtualization) IsValidVMName(name string) (bool, error) {
infos, err := getVMInfos()
if err != nil {
return false, err
}
for _, vm := range infos {
if vm.Name == name {
return true, nil
}
}
return false, nil
}
// CheckExclusiveActiveVM checks if there is a VM already running
// that does not allow other VMs to be running
func (p *Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
vms, err := getVMInfos()
if err != nil {
return false, "", fmt.Errorf("checking VM active: %w", err)
}
for _, vm := range vms {
if vm.Running || vm.Starting {
return true, vm.Name, nil
}
}
return false, "", nil
}
func (p *Virtualization) Artifact() machine.Artifact {
return p.artifact
}
func (p *Virtualization) Compression() machine.ImageCompression {
return p.compression
}
func (p *Virtualization) Format() machine.ImageFormat {
return p.format
}
// startHostNetworking runs a binary on the host system that allows users
// to set up port forwarding to the podman virtual machine
func (v *MachineVM) startHostNetworking() (string, machine.APIForwardingState, error) {
@ -1695,86 +1513,6 @@ func (v *MachineVM) editCmdLine(flag string, value string) {
}
}
// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
func (p *Virtualization) 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 := machine.GuardedRemoveAll(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 := machine.GuardedRemoveAll(confDir)
if err != nil {
if prevErr != nil {
logrus.Error(prevErr)
}
prevErr = err
}
}
return prevErr
}
func (p *Virtualization) VMType() machine.VMType {
return vmtype
}
func isRootful() bool {
// Rootless is not relevant on Windows. In the future rootless.IsRootless
// could be switched to return true on Windows, and other codepaths migrated

213
pkg/machine/wsl/config.go Normal file
View File

@ -0,0 +1,213 @@
//go:build windows
// +build windows
package wsl
import (
"io/fs"
"path/filepath"
"strings"
"time"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/utils"
"github.com/sirupsen/logrus"
)
type WSLVirtualization struct {
machine.Virtualization
}
func VirtualizationProvider() machine.VirtProvider {
return &WSLVirtualization{
machine.NewVirtualization(machine.None, machine.Xz, machine.Tar),
}
}
// NewMachine initializes an instance of a wsl machine
func (p *WSLVirtualization) 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
// Default is false
if opts.UserModeNetworking != nil {
vm.UserModeNetworking = *opts.UserModeNetworking
}
// Add a random port for ssh
port, err := utils.GetRandomPort()
if err != nil {
return nil, err
}
vm.Port = port
return vm, nil
}
// LoadByName reads a json file that describes a known qemu vm
// and returns a vm instance
func (p *WSLVirtualization) LoadVMByName(name string) (machine.VM, error) {
configPath, err := getConfigPath(name)
if err != nil {
return nil, err
}
vm, err := readAndMigrate(configPath, name)
return vm, err
}
// List lists all vm's that use qemu virtualization
func (p *WSLVirtualization) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
return GetVMInfos()
}
func GetVMInfos() ([]*machine.ListResponse, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
}
var listed []*machine.ListResponse
if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
if strings.HasSuffix(d.Name(), ".json") {
path := filepath.Join(vmConfigDir, d.Name())
vm, err := readAndMigrate(path, strings.TrimSuffix(d.Name(), ".json"))
if err != nil {
return err
}
listEntry := new(machine.ListResponse)
listEntry.Name = vm.Name
listEntry.Stream = vm.ImageStream
listEntry.VMType = "wsl"
listEntry.CPUs, _ = getCPUs(vm)
listEntry.Memory, _ = getMem(vm)
listEntry.DiskSize = getDiskSize(vm)
listEntry.RemoteUsername = vm.RemoteUsername
listEntry.Port = vm.Port
listEntry.IdentityPath = vm.IdentityPath
listEntry.Starting = false
listEntry.UserModeNetworking = vm.UserModeNetworking
running := vm.isRunning()
listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running)
listEntry.Running = running
listed = append(listed, listEntry)
}
return nil
}); err != nil {
return nil, err
}
return listed, err
}
func (p *WSLVirtualization) IsValidVMName(name string) (bool, error) {
infos, err := GetVMInfos()
if err != nil {
return false, err
}
for _, vm := range infos {
if vm.Name == name {
return true, nil
}
}
return false, nil
}
func (p *WSLVirtualization) CheckExclusiveActiveVM() (bool, string, error) {
return false, "", nil
}
// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
func (p *WSLVirtualization) 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 := machine.GuardedRemoveAll(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 := machine.GuardedRemoveAll(confDir)
if err != nil {
if prevErr != nil {
logrus.Error(prevErr)
}
prevErr = err
}
}
return prevErr
}
func (p *WSLVirtualization) VMType() machine.VMType {
return vmtype
}

View File

@ -9,7 +9,6 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net/url"
"os"
"os/exec"
@ -22,7 +21,6 @@ import (
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/wsl/wutil"
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/podman/v4/utils"
"github.com/containers/storage/pkg/homedir"
"github.com/containers/storage/pkg/ioutils"
"github.com/sirupsen/logrus"
@ -219,24 +217,6 @@ const (
rootlessSock = "/run/user/1000/podman/podman.sock"
)
type Virtualization struct {
artifact machine.Artifact
compression machine.ImageCompression
format machine.ImageFormat
}
func (p *Virtualization) Artifact() machine.Artifact {
return p.artifact
}
func (p *Virtualization) Compression() machine.ImageCompression {
return p.compression
}
func (p *Virtualization) Format() machine.ImageFormat {
return p.format
}
type MachineVM struct {
// ConfigPath is the path to the configuration file
ConfigPath string
@ -268,46 +248,6 @@ func (e *ExitCodeError) Error() string {
return fmt.Sprintf("Process failed with exit code: %d", e.code)
}
func GetWSLProvider() machine.VirtProvider {
return &Virtualization{
artifact: machine.None,
compression: machine.Xz,
format: machine.Tar,
}
}
// NewMachine initializes an instance of a wsl machine
func (p *Virtualization) 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
// Default is false
if opts.UserModeNetworking != nil {
vm.UserModeNetworking = *opts.UserModeNetworking
}
// Add a random port for ssh
port, err := utils.GetRandomPort()
if err != nil {
return nil, err
}
vm.Port = port
return vm, nil
}
func getConfigPath(name string) (string, error) {
return getConfigPathExt(name, "json")
}
@ -321,18 +261,6 @@ func getConfigPathExt(name string, extension string) (string, error) {
return filepath.Join(vmConfigDir, fmt.Sprintf("%s.%s", name, extension)), nil
}
// LoadByName reads a json file that describes a known qemu vm
// and returns a vm instance
func (p *Virtualization) LoadVMByName(name string) (machine.VM, error) {
configPath, err := getConfigPath(name)
if err != nil {
return nil, err
}
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) {
@ -1512,53 +1440,6 @@ func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
return cmd.Run()
}
// List lists all vm's that use qemu virtualization
func (p *Virtualization) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
return GetVMInfos()
}
func GetVMInfos() ([]*machine.ListResponse, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
}
var listed []*machine.ListResponse
if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
if strings.HasSuffix(d.Name(), ".json") {
path := filepath.Join(vmConfigDir, d.Name())
vm, err := readAndMigrate(path, strings.TrimSuffix(d.Name(), ".json"))
if err != nil {
return err
}
listEntry := new(machine.ListResponse)
listEntry.Name = vm.Name
listEntry.Stream = vm.ImageStream
listEntry.VMType = "wsl"
listEntry.CPUs, _ = getCPUs(vm)
listEntry.Memory, _ = getMem(vm)
listEntry.DiskSize = getDiskSize(vm)
listEntry.RemoteUsername = vm.RemoteUsername
listEntry.Port = vm.Port
listEntry.IdentityPath = vm.IdentityPath
listEntry.Starting = false
listEntry.UserModeNetworking = vm.UserModeNetworking
running := vm.isRunning()
listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running)
listEntry.Running = running
listed = append(listed, listEntry)
}
return nil
}); err != nil {
return nil, err
}
return listed, err
}
func (vm *MachineVM) updateTimeStamps(updateLast bool) (time.Time, time.Time, error) {
var err error
if updateLast {
@ -1643,23 +1524,6 @@ func getMem(vm *MachineVM) (uint64, error) {
return total - available, err
}
func (p *Virtualization) IsValidVMName(name string) (bool, error) {
infos, err := GetVMInfos()
if err != nil {
return false, err
}
for _, vm := range infos {
if vm.Name == name {
return true, nil
}
}
return false, nil
}
func (p *Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
return false, "", nil
}
func (v *MachineVM) setRootful(rootful bool) error {
changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
if err != nil {
@ -1716,83 +1580,3 @@ func (v *MachineVM) getResources() (resources machine.ResourceConfig) {
resources.DiskSize = getDiskSize(v)
return
}
// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
func (p *Virtualization) 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 := machine.GuardedRemoveAll(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 := machine.GuardedRemoveAll(confDir)
if err != nil {
if prevErr != nil {
logrus.Error(prevErr)
}
prevErr = err
}
}
return prevErr
}
func (p *Virtualization) VMType() machine.VMType {
return vmtype
}