Merge pull request #20988 from baude/p5standardmachineconfig

Podman 5 machine config file - Step 1
This commit is contained in:
openshift-merge-bot[bot]
2023-12-12 10:01:29 +00:00
committed by GitHub
30 changed files with 721 additions and 528 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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

View File

@@ -0,0 +1,3 @@
package define
const UserCertsTargetPath = "/etc/containers/certs.d"

View File

@@ -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"

View File

@@ -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

View File

@@ -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())

View File

@@ -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())
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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{

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -1,7 +1,7 @@
//go:build (amd64 && !windows) || (arm64 && !windows)
// +build amd64,!windows arm64,!windows
package qemu
package command
import (
"fmt"

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}
}
}

View File

@@ -1,4 +1,4 @@
package machine
package sockets
import (
"bufio"

View File

@@ -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
}

View File

@@ -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{}

View File

@@ -0,0 +1,7 @@
package vmconfigs
// Stubs
type HyperVConfig struct{}
type WSLConfig struct {}
type QEMUConfig struct {}
type AppleHVConfig struct {}

View File

@@ -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{}

View File

@@ -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{}

View File

@@ -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,

View File

@@ -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)