Podman 5 machine config file - Step 1

The following PR is the very first step in what will a series of steps
to apply a "common" machine configuration file to all providers.
Function names, method names, struct names, and field names are all up
for debate.  The purpose of this PR is to offer a glimpse at the
direction we intend to take.

This PR also contains temporary structs (i.e. aThing) that are not
exported.  These are merely placeholders.

The configuration work in this PR is also unused of yet.  But the code
is compiled.  Once merged, we can begin the next step of development.

[NO NEW TESTS NEEDED]

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2023-11-22 07:59:25 -06:00
parent 9877dc4980
commit e5a4f00b7d
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/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/podman/v4/pkg/machine" "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/containers/podman/v4/pkg/machine/provider"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -184,7 +185,7 @@ func composeDockerHost() (string, error) {
if err != nil { if err != nil {
return "", fmt.Errorf("inspecting machine: %w", err) 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) return "", fmt.Errorf("machine %s is not running but in state %s", item.Name, info.State)
} }
if machineProvider.VMType() == machine.WSLVirt { 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"
"github.com/containers/podman/v4/pkg/machine/compression" "github.com/containers/podman/v4/pkg/machine/compression"
"github.com/containers/podman/v4/pkg/machine/define" "github.com/containers/podman/v4/pkg/machine/define"
"github.com/containers/podman/v4/pkg/machine/vmconfigs"
vfConfig "github.com/crc-org/vfkit/pkg/config" vfConfig "github.com/crc-org/vfkit/pkg/config"
"github.com/docker/go-units" "github.com/docker/go-units"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -76,10 +77,10 @@ func (v AppleHVVirtualization) List(opts machine.ListOptions) ([]*machine.ListRe
} }
for _, mm := range mms { for _, mm := range mms {
vmState, err := mm.Vfkit.state() vmState, err := mm.Vfkit.State()
if err != nil { if err != nil {
if errors.Is(err, unix.ECONNREFUSED) { if errors.Is(err, unix.ECONNREFUSED) {
vmState = machine.Stopped vmState = define.Stopped
} else { } else {
return nil, err return nil, err
} }
@ -89,8 +90,8 @@ func (v AppleHVVirtualization) List(opts machine.ListOptions) ([]*machine.ListRe
Name: mm.Name, Name: mm.Name,
CreatedAt: mm.Created, CreatedAt: mm.Created,
LastUp: mm.LastUp, LastUp: mm.LastUp,
Running: vmState == machine.Running, Running: vmState == define.Running,
Starting: vmState == machine.Starting, Starting: vmState == define.Starting,
Stream: mm.ImageStream, Stream: mm.ImageStream,
VMType: machine.AppleHvVirt.String(), VMType: machine.AppleHvVirt.String(),
CPUs: mm.CPUs, CPUs: mm.CPUs,
@ -140,7 +141,7 @@ func (v AppleHVVirtualization) NewMachine(opts machine.InitOptions) (machine.VM,
// Set creation time // Set creation time
m.Created = time.Now() m.Created = time.Now()
m.ResourceConfig = machine.ResourceConfig{ m.ResourceConfig = vmconfigs.ResourceConfig{
CPUs: opts.CPUS, CPUs: opts.CPUS,
DiskSize: opts.DiskSize, DiskSize: opts.DiskSize,
// Diskpath will be needed // Diskpath will be needed

View File

@ -21,7 +21,10 @@ import (
"github.com/containers/common/pkg/config" "github.com/containers/common/pkg/config"
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/podman/v4/pkg/machine" "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/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/strongunits"
"github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/pkg/util"
"github.com/containers/podman/v4/utils" "github.com/containers/podman/v4/utils"
@ -43,14 +46,6 @@ const (
apiUpTimeout = 20 * time.Second 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 // appleHVReadyUnit is a unit file that sets up the virtual serial device
// where when the VM is done configuring, it will send an ack // where when the VM is done configuring, it will send an ack
// so a listening host knows it can begin interacting with it // 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 is the fully qualified path to the configuration file
ConfigPath define.VMFile ConfigPath define.VMFile
// HostUser contains info about host user // HostUser contains info about host user
machine.HostUser vmconfigs.HostUser
// ImageConfig describes the bootable image // ImageConfig describes the bootable image
machine.ImageConfig machine.ImageConfig
// Mounts is the list of remote filesystems to mount // Mounts is the list of remote filesystems to mount
Mounts []machine.Mount Mounts []vmconfigs.Mount
// Name of VM // Name of VM
Name string Name string
// ReadySocket tells host when vm is booted // ReadySocket tells host when vm is booted
ReadySocket define.VMFile ReadySocket define.VMFile
// ResourceConfig is physical attrs of the VM // ResourceConfig is physical attrs of the VM
machine.ResourceConfig vmconfigs.ResourceConfig
// SSHConfig for accessing the remote vm // 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 tells us whether the machine is running or if we have just dialed it to start it
Starting bool Starting bool
// Created contains the original created time instead of querying the file mod time // 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 contains the last recorded uptime
LastUp time.Time LastUp time.Time
// The VFKit endpoint where we can interact with the VM // The VFKit endpoint where we can interact with the VM
Vfkit VfkitHelper Vfkit vfkit.VfkitHelper
LogPath define.VMFile LogPath define.VMFile
GvProxyPid define.VMFile GvProxyPid define.VMFile
GvProxySock define.VMFile GvProxySock define.VMFile
@ -108,7 +103,7 @@ func (m *MacMachine) setGVProxyInfo(runtimeDir string) error {
} }
m.GvProxyPid = *gvProxyPid 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 // 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 // addMountsToVM converts the volumes passed through the CLI to virtio-fs mounts
// and adds them to the machine // and adds them to the machine
func (m *MacMachine) addMountsToVM(opts machine.InitOptions, virtiofsMnts *[]machine.VirtIoFs) error { func (m *MacMachine) addMountsToVM(opts machine.InitOptions, virtiofsMnts *[]machine.VirtIoFs) error {
var mounts []machine.Mount var mounts []vmconfigs.Mount
for _, volume := range opts.Volumes { for _, volume := range opts.Volumes {
source, target, _, readOnly, err := machine.ParseVolumeFromPath(volume) source, target, _, readOnly, err := machine.ParseVolumeFromPath(volume)
if err != nil { if err != nil {
@ -202,7 +197,7 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
return false, err 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 return false, err
} }
@ -305,7 +300,7 @@ func (m *MacMachine) removeSystemConnections() error {
} }
func (m *MacMachine) Inspect() (*machine.InspectInfo, error) { func (m *MacMachine) Inspect() (*machine.InspectInfo, error) {
vmState, err := m.Vfkit.state() vmState, err := m.Vfkit.State()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -329,7 +324,7 @@ func (m *MacMachine) Inspect() (*machine.InspectInfo, error) {
}, },
LastUp: m.LastUp, LastUp: m.LastUp,
Name: m.Name, Name: m.Name,
Resources: machine.ResourceConfig{ Resources: vmconfigs.ResourceConfig{
CPUs: m.CPUs, CPUs: m.CPUs,
DiskSize: m.DiskSize, DiskSize: m.DiskSize,
Memory: m.Memory, Memory: m.Memory,
@ -367,16 +362,16 @@ func (m *MacMachine) Remove(name string, opts machine.RemoveOptions) (string, fu
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
vmState, err := m.Vfkit.state() vmState, err := m.Vfkit.State()
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
if vmState == machine.Running { if vmState == define.Running {
if !opts.Force { if !opts.Force {
return "", nil, &machine.ErrVMRunningCannotDestroyed{Name: m.Name} 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 return "", nil, err
} }
defer func() { defer func() {
@ -430,7 +425,7 @@ func (m *MacMachine) Set(name string, opts machine.SetOptions) ([]error, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if vmState != machine.Stopped { if vmState != define.Stopped {
return nil, machine.ErrWrongState return nil, machine.ErrWrongState
} }
if cpus := opts.CPUs; cpus != nil { if cpus := opts.CPUs; cpus != nil {
@ -473,7 +468,7 @@ func (m *MacMachine) SSH(name string, opts machine.SSHOptions) error {
if err != nil { if err != nil {
return err return err
} }
if st != machine.Running { if st != define.Running {
return fmt.Errorf("vm %q is not running", m.Name) return fmt.Errorf("vm %q is not running", m.Name)
} }
username := opts.Username username := opts.Username
@ -561,7 +556,7 @@ func (m *MacMachine) Start(name string, opts machine.StartOptions) error {
return err return err
} }
if st == machine.Running { if st == define.Running {
return machine.ErrVMAlreadyRunning return machine.ErrVMAlreadyRunning
} }
@ -664,7 +659,7 @@ func (m *MacMachine) Start(name string, opts machine.StartOptions) error {
logrus.Debug("waiting for ready notification") logrus.Debug("waiting for ready notification")
readyChan := make(chan error) readyChan := make(chan error)
go machine.ListenAndWaitOnSocket(readyChan, readyListen) go sockets.ListenAndWaitOnSocket(readyChan, readyListen)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return err return err
@ -715,8 +710,8 @@ func (m *MacMachine) Start(name string, opts machine.StartOptions) error {
return nil return nil
} }
func (m *MacMachine) State(_ bool) (machine.Status, error) { func (m *MacMachine) State(_ bool) (define.Status, error) {
vmStatus, err := m.Vfkit.state() vmStatus, err := m.Vfkit.State()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -732,7 +727,7 @@ func (m *MacMachine) Stop(name string, opts machine.StopOptions) error {
return err return err
} }
if vmState != machine.Running { if vmState != define.Running {
return nil 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 // getVMConfigPath is a simple wrapper for getting the fully-qualified
@ -845,7 +840,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
if err != nil { if err != nil {
return err return err
} }
listEntry.Running = vmState == machine.Running listEntry.Running = vmState == define.Running
listEntry.LastUp = vm.LastUp listEntry.LastUp = vm.LastUp
listed = append(listed, listEntry) listed = append(listed, listEntry)

View File

@ -1,7 +1,7 @@
//go:build darwin //go:build darwin
// +build darwin // +build darwin
package applehv package vfkit
import ( import (
"bytes" "bytes"
@ -12,14 +12,13 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/define"
"github.com/crc-org/vfkit/pkg/rest/define" "github.com/crc-org/vfkit/pkg/config"
rest "github.com/crc-org/vfkit/pkg/rest/define"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
type Endpoint string
const ( const (
inspect = "/vm/inspect" inspect = "/vm/inspect"
state = "/vm/state" 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()) // getRawState asks vfkit for virtual machine state unmodified (see state())
func (vf *VfkitHelper) getRawState() (machine.Status, error) { func (vf *VfkitHelper) getRawState() (define.Status, error) {
var response define.VMState var response rest.VMState
endPoint := vf.Endpoint + state endPoint := vf.Endpoint + state
serverResponse, err := vf.get(endPoint, nil) serverResponse, err := vf.get(endPoint, nil)
if err != nil { if err != nil {
@ -60,25 +59,24 @@ func (vf *VfkitHelper) getRawState() (machine.Status, error) {
return "", err return "", err
} }
return ToMachineStatus(response.State) return ToMachineStatus(response.State)
} }
// state asks vfkit for the virtual machine state. in case the vfkit // state asks vfkit for the virtual machine state. in case the vfkit
// service is not responding, we assume the service is not running // service is not responding, we assume the service is not running
// and return a stopped status // and return a stopped status
func (vf *VfkitHelper) state() (machine.Status, error) { func (vf *VfkitHelper) State() (define.Status, error) {
vmState, err := vf.getRawState() vmState, err := vf.getRawState()
if err == nil { if err == nil {
return vmState, err return vmState, err
} }
if errors.Is(err, unix.ECONNREFUSED) { if errors.Is(err, unix.ECONNREFUSED) {
return machine.Stopped, nil return define.Stopped, nil
} }
return "", err return "", err
} }
func (vf *VfkitHelper) stateChange(newState define.StateChange) error { func (vf *VfkitHelper) stateChange(newState rest.StateChange) error {
b, err := json.Marshal(define.VMState{State: string(newState)}) b, err := json.Marshal(rest.VMState{State: string(newState)})
if err != nil { if err != nil {
return err return err
} }
@ -87,15 +85,15 @@ func (vf *VfkitHelper) stateChange(newState define.StateChange) error {
return err return err
} }
func (vf *VfkitHelper) stop(force, wait bool) error { func (vf *VfkitHelper) Stop(force, wait bool) error {
waitDuration := time.Millisecond * 10 waitDuration := time.Millisecond * 10
// TODO Add ability to wait until stopped // TODO Add ability to wait until stopped
if force { if force {
if err := vf.stateChange(define.HardStop); err != nil { if err := vf.stateChange(rest.HardStop); err != nil {
return err return err
} }
} else { } else {
if err := vf.stateChange(define.Stop); err != nil { if err := vf.stateChange(rest.Stop); err != nil {
return err return err
} }
} }
@ -116,3 +114,11 @@ func (vf *VfkitHelper) stop(force, wait bool) error {
} }
return waitErr 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 //go:build darwin
// +build darwin // +build darwin
package applehv package vfkit
import ( import (
"errors" "errors"
"fmt" "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 // VZMachineState is what the restful service in vfkit will return
type VZMachineState string type VZMachineState string
@ -26,14 +28,14 @@ const (
VZMachineStateStopping VZMachineState = "VirtualMachineStateStopping" VZMachineStateStopping VZMachineState = "VirtualMachineStateStopping"
) )
func ToMachineStatus(val string) (machine.Status, error) { func ToMachineStatus(val string) (define.Status, error) {
switch val { switch val {
case string(VZMachineStateRunning), string(VZMachineStatePausing), string(VZMachineStateResuming), string(VZMachineStateStopping), string(VZMachineStatePaused): case string(VZMachineStateRunning), string(VZMachineStatePausing), string(VZMachineStateResuming), string(VZMachineStateStopping), string(VZMachineStatePaused):
return machine.Running, nil return define.Running, nil
case string(VZMachineStateStopped): case string(VZMachineStateStopped):
return machine.Stopped, nil return define.Stopped, nil
case string(VZMachineStateStarting): case string(VZMachineStateStarting):
return machine.Starting, nil return define.Starting, nil
case string(VZMachineStateError): case string(VZMachineStateError):
return "", errors.New("machine is in error state") return "", errors.New("machine is in error state")
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/containers/common/pkg/machine" "github.com/containers/common/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/compression" "github.com/containers/podman/v4/pkg/machine/compression"
"github.com/containers/podman/v4/pkg/machine/define" "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/homedir"
"github.com/containers/storage/pkg/lockfile" "github.com/containers/storage/pkg/lockfile"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -43,17 +44,7 @@ type InitOptions struct {
USBs []string USBs []string
} }
type Status = string
const ( 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" DefaultMachineName string = "podman-machine-default"
apiUpTimeout = 20 * time.Second apiUpTimeout = 20 * time.Second
) )
@ -139,7 +130,7 @@ type VM interface {
Set(name string, opts SetOptions) ([]error, error) Set(name string, opts SetOptions) ([]error, error)
SSH(name string, opts SSHOptions) error SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error Start(name string, opts StartOptions) error
State(bypass bool) (Status, error) State(bypass bool) (define.Status, error)
Stop(name string, opts StopOptions) error Stop(name string, opts StopOptions) error
} }
@ -173,9 +164,9 @@ type InspectInfo struct {
Image ImageConfig Image ImageConfig
LastUp time.Time LastUp time.Time
Name string Name string
Resources ResourceConfig Resources vmconfigs.ResourceConfig
SSHConfig SSHConfig SSHConfig vmconfigs.SSHConfig
State Status State define.Status
UserModeNetworking bool UserModeNetworking bool
Rootful bool Rootful bool
} }
@ -274,33 +265,6 @@ func ConfDirPrefix() (string, error) {
return confDir, nil 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 // ImageConfig describes the bootable image for the VM
type ImageConfig struct { type ImageConfig struct {
// IgnitionFile is the path to the filesystem where the // IgnitionFile is the path to the filesystem where the
@ -312,26 +276,6 @@ type ImageConfig struct {
ImagePath define.VMFile `json:"ImagePath"` 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. // ConnectionConfig contains connections like sockets, etc.
type ConnectionConfig struct { type ConnectionConfig struct {
// PodmanSocket is the exported podman service socket // 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" "time"
"github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/define"
"github.com/containers/podman/v4/utils" "github.com/containers/podman/v4/utils"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -108,7 +109,7 @@ var _ = Describe("podman machine init", func() {
Expect(ec).To(BeZero()) Expect(ec).To(BeZero())
Expect(inspectBefore).ToNot(BeEmpty()) Expect(inspectBefore).ToNot(BeEmpty())
Expect(inspectAfter).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 if isWSL() { // WSL does not use FCOS
return return

View File

@ -1,7 +1,7 @@
package e2e_test package e2e_test
import ( import (
"github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/define"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec" . "github.com/onsi/gomega/gexec"
@ -32,7 +32,7 @@ var _ = Describe("podman machine start", func() {
info, ec, err := mb.toQemuInspectInfo() info, ec, err := mb.toQemuInspectInfo()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(ec).To(BeZero()) Expect(ec).To(BeZero())
Expect(info[0].State).To(Equal(machine.Running)) Expect(info[0].State).To(Equal(define.Running))
stop := new(stopMachine) stop := new(stopMachine)
stopSession, err := mb.setCmd(stop).run() stopSession, err := mb.setCmd(stop).run()
@ -77,7 +77,7 @@ var _ = Describe("podman machine start", func() {
info, ec, err := mb.toQemuInspectInfo() info, ec, err := mb.toQemuInspectInfo()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(ec).To(BeZero()) 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() startSession, err = mb.setCmd(s).run()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())

View File

@ -286,14 +286,14 @@ func handlePrevError(e, prevErr error) error {
return e return e
} }
func stateConversion(s hypervctl.EnabledState) (machine.Status, error) { func stateConversion(s hypervctl.EnabledState) (define.Status, error) {
switch s { switch s {
case hypervctl.Enabled: case hypervctl.Enabled:
return machine.Running, nil return define.Running, nil
case hypervctl.Disabled: case hypervctl.Disabled:
return machine.Stopped, nil return define.Stopped, nil
case hypervctl.Starting: 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/libhvee/pkg/hypervctl"
"github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/define" "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/strongunits"
"github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/pkg/util"
"github.com/containers/podman/v4/utils" "github.com/containers/podman/v4/utils"
@ -99,21 +101,21 @@ type HyperVMachine struct {
// ConfigPath is the fully qualified path to the configuration file // ConfigPath is the fully qualified path to the configuration file
ConfigPath define.VMFile ConfigPath define.VMFile
// HostUser contains info about host user // HostUser contains info about host user
machine.HostUser vmconfigs.HostUser
// ImageConfig describes the bootable image // ImageConfig describes the bootable image
machine.ImageConfig machine.ImageConfig
// Mounts is the list of remote filesystems to mount // Mounts is the list of remote filesystems to mount
Mounts []machine.Mount Mounts []vmconfigs.Mount
// Name of VM // Name of VM
Name string Name string
// NetworkVSock is for the user networking // NetworkVSock is for the user networking
NetworkHVSock HVSockRegistryEntry NetworkHVSock vsock.HVSockRegistryEntry
// ReadySocket tells host when vm is booted // ReadySocket tells host when vm is booted
ReadyHVSock HVSockRegistryEntry ReadyHVSock vsock.HVSockRegistryEntry
// ResourceConfig is physical attrs of the VM // ResourceConfig is physical attrs of the VM
machine.ResourceConfig vmconfigs.ResourceConfig
// SSHConfig for accessing the remote vm // 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 tells us whether the machine is running or if we have just dialed it to start it
Starting bool Starting bool
// Created contains the original created time instead of querying the file mod time // 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 // addNetworkAndReadySocketsToRegistry adds the Network and Ready sockets to the
// Windows registry // Windows registry
func (m *HyperVMachine) addNetworkAndReadySocketsToRegistry() error { func (m *HyperVMachine) addNetworkAndReadySocketsToRegistry() error {
networkHVSock, err := NewHVSockRegistryEntry(m.Name, Network) networkHVSock, err := vsock.NewHVSockRegistryEntry(m.Name, vsock.Network)
if err != nil { if err != nil {
return err return err
} }
eventHVSocket, err := NewHVSockRegistryEntry(m.Name, Events) eventHVSocket, err := vsock.NewHVSockRegistryEntry(m.Name, vsock.Events)
if err != nil { if err != nil {
return err return err
} }
@ -185,7 +187,7 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
// around to those, would be another : after that. // around to those, would be another : after that.
// TODO: Need to support options here // TODO: Need to support options here
for _, mount := range opts.Volumes { for _, mount := range opts.Volumes {
newMount := machine.Mount{} newMount := vmconfigs.Mount{}
splitMount := strings.Split(mount, ":") splitMount := strings.Split(mount, ":")
if len(splitMount) < 3 { if len(splitMount) < 3 {
@ -242,7 +244,7 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
callbackFuncs.Add(m.removeSSHKeys) callbackFuncs.Add(m.removeSSHKeys)
} }
m.ResourceConfig = machine.ResourceConfig{ m.ResourceConfig = vmconfigs.ResourceConfig{
CPUs: opts.CPUS, CPUs: opts.CPUS,
DiskSize: opts.DiskSize, DiskSize: opts.DiskSize,
Memory: opts.Memory, Memory: opts.Memory,
@ -367,7 +369,7 @@ func (m *HyperVMachine) Inspect() (*machine.InspectInfo, error) {
}, },
LastUp: m.LastUp, LastUp: m.LastUp,
Name: m.Name, Name: m.Name,
Resources: machine.ResourceConfig{ Resources: vmconfigs.ResourceConfig{
CPUs: uint64(cfg.Hardware.CPUs), CPUs: uint64(cfg.Hardware.CPUs),
DiskSize: 0, DiskSize: 0,
Memory: cfg.Hardware.Memory, Memory: cfg.Hardware.Memory,
@ -543,7 +545,7 @@ func (m *HyperVMachine) SSH(name string, opts machine.SSHOptions) error {
if err != nil { if err != nil {
return err return err
} }
if state != machine.Running { if state != define.Running {
return fmt.Errorf("vm %q is not running", m.Name) 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() return m.writeConfig()
} }
func (m *HyperVMachine) State(_ bool) (machine.Status, error) { func (m *HyperVMachine) State(_ bool) (define.Status, error) {
vmm := hypervctl.NewVirtualMachineManager() vmm := hypervctl.NewVirtualMachineManager()
vm, err := vmm.GetMachine(m.Name) vm, err := vmm.GetMachine(m.Name)
if err != nil { if err != nil {
return "", err return "", err
} }
if vm.IsStarting() { if vm.IsStarting() {
return machine.Starting, nil return define.Starting, nil
} }
if vm.State() == hypervctl.Enabled { if vm.State() == hypervctl.Enabled {
return machine.Running, nil return define.Running, nil
} }
// Following QEMU pattern here where only three // Following QEMU pattern here where only three
// states seem valid // states seem valid
return machine.Stopped, nil return define.Stopped, nil
} }
func (m *HyperVMachine) Stop(name string, opts machine.StopOptions) error { 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) toReturn := make(map[string]uint64)
for _, mount := range m.Mounts { for _, mount := range m.Mounts {
var vsock *HVSockRegistryEntry var hvSock *vsock.HVSockRegistryEntry
vsockNum, ok := m.MountVsocks[mount.Target] vsockNum, ok := m.MountVsocks[mount.Target]
if ok { if ok {
// Ignore errors here, we'll just try and recreate the // Ignore errors here, we'll just try and recreate the
// vsock below. // vsock below.
testVsock, err := LoadHVSockRegistryEntry(vsockNum) testVsock, err := vsock.LoadHVSockRegistryEntry(vsockNum)
if err == nil { if err == nil {
vsock = testVsock hvSock = testVsock
} }
} }
if vsock == nil { if hvSock == nil {
testVsock, err := NewHVSockRegistryEntry(m.Name, Fileserver) testVsock, err := vsock.NewHVSockRegistryEntry(m.Name, vsock.Fileserver)
if err != nil { if err != nil {
return nil, err 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 return toReturn, nil
@ -955,7 +957,7 @@ func (m *HyperVMachine) removeShares() error {
continue continue
} }
vsock, err := LoadHVSockRegistryEntry(vsockNum) vsock, err := vsock.LoadHVSockRegistryEntry(vsockNum)
if err != nil { if err != nil {
logrus.Debugf("Vsock %d for mountpoint %s does not have a valid registry entry, skipping removal", vsockNum, mount.Target) logrus.Debugf("Vsock %d for mountpoint %s does not have a valid registry entry, skipping removal", vsockNum, mount.Target)
continue continue

View File

@ -1,7 +1,7 @@
//go:build windows //go:build windows
// +build windows // +build windows
package hyperv package vsock
import ( import (
"errors" "errors"
@ -9,8 +9,9 @@ import (
"net" "net"
"strings" "strings"
"github.com/containers/podman/v4/pkg/machine/sockets"
"github.com/Microsoft/go-winio" "github.com/Microsoft/go-winio"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/utils" "github.com/containers/podman/v4/utils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
@ -274,7 +275,7 @@ func (hv *HVSockRegistryEntry) Listen() error {
}() }()
errChan := make(chan error) errChan := make(chan error)
go machine.ListenAndWaitOnSocket(errChan, listener) go sockets.ListenAndWaitOnSocket(errChan, listener)
return <-errChan return <-errChan
} }

View File

@ -10,10 +10,7 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "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/containers/podman/v4/pkg/machine/define"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -28,7 +25,6 @@ import (
*/ */
const ( const (
UserCertsTargetPath = "/etc/containers/certs.d"
PodmanDockerTmpConfPath = "/etc/tmpfiles.d/podman-docker.conf" PodmanDockerTmpConfPath = "/etc/tmpfiles.d/podman-docker.conf"
) )
@ -615,7 +611,7 @@ func prepareCertFile(path string, name string) (File, error) {
return File{}, err 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) 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 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 { func getLinks(usrName string) []Link {
return []Link{{ return []Link{{
Node: Node{ 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) //go:build (amd64 && !windows) || (arm64 && !windows)
// +build amd64,!windows arm64,!windows // +build amd64,!windows arm64,!windows
package qemu package command
import ( import (
"fmt" "fmt"

View File

@ -6,7 +6,6 @@ import (
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
@ -14,6 +13,9 @@ import (
"github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/compression" "github.com/containers/podman/v4/pkg/machine/compression"
"github.com/containers/podman/v4/pkg/machine/define" "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/containers/podman/v4/utils"
"github.com/docker/go-units" "github.com/docker/go-units"
"github.com/sirupsen/logrus" "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 // setNewMachineCMD configure the CLI command that will be run to create the new
// machine // machine
func (v *MachineVM) setNewMachineCMD(qemuBinary string, cmdOpts *setNewMachineCMDOpts) { 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.SetMemory(v.Memory)
v.CmdLine.SetCPUs(v.CPUs) v.CmdLine.SetCPUs(v.CPUs)
v.CmdLine.SetIgnitionFile(v.IgnitionFile) v.CmdLine.SetIgnitionFile(v.IgnitionFile)
@ -69,69 +71,6 @@ func (v *MachineVM) setNewMachineCMD(qemuBinary string, cmdOpts *setNewMachineCM
v.CmdLine.SetUSBHostPassthrough(v.USBs) 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 // NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization. // virtualization.
func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) { 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.CPUs = opts.CPUS
vm.Memory = opts.Memory vm.Memory = opts.Memory
vm.DiskSize = opts.DiskSize 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 return nil, err
} }
@ -195,7 +134,7 @@ func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, e
return nil, err return nil, err
} }
symlink := vm.Name + "_ready.sock" 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 return nil, err
} }
@ -209,7 +148,7 @@ func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, e
// and returns a vm instance // and returns a vm instance
func (p *QEMUVirtualization) LoadVMByName(name string) (machine.VM, error) { func (p *QEMUVirtualization) LoadVMByName(name string) (machine.VM, error) {
vm := &MachineVM{Name: name} 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 { if err := vm.update(); err != nil {
return nil, err return nil, err
} }
@ -274,7 +213,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
if err != nil { if err != nil {
return err return err
} }
listEntry.Running = state == machine.Running listEntry.Running = state == define.Running
listEntry.LastUp = vm.LastUp listEntry.LastUp = vm.LastUp
listed = append(listed, listEntry) listed = append(listed, listEntry)

View File

@ -4,20 +4,20 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/qemu/command"
) )
func TestUSBParsing(t *testing.T) { func TestUSBParsing(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args []string args []string
result []machine.USBConfig result []command.USBConfig
wantErr bool wantErr bool
}{ }{
{ {
name: "Good vendor and product", name: "Good vendor and product",
args: []string{"vendor=13d3,product=5406", "vendor=08ec,product=0016"}, args: []string{"vendor=13d3,product=5406", "vendor=08ec,product=0016"},
result: []machine.USBConfig{ result: []command.USBConfig{
{ {
Vendor: 5075, Vendor: 5075,
Product: 21510, Product: 21510,
@ -32,7 +32,7 @@ func TestUSBParsing(t *testing.T) {
{ {
name: "Good bus and device number", name: "Good bus and device number",
args: []string{"bus=1,devnum=4", "bus=1,devnum=3"}, args: []string{"bus=1,devnum=4", "bus=1,devnum=3"},
result: []machine.USBConfig{ result: []command.USBConfig{
{ {
Bus: "1", Bus: "1",
DevNumber: "4", DevNumber: "4",
@ -47,26 +47,26 @@ func TestUSBParsing(t *testing.T) {
{ {
name: "Bad vendor and product, not hexa", name: "Bad vendor and product, not hexa",
args: []string{"vendor=13dk,product=5406"}, args: []string{"vendor=13dk,product=5406"},
result: []machine.USBConfig{}, result: []command.USBConfig{},
wantErr: true, wantErr: true,
}, },
{ {
name: "Bad vendor and product, bad separator", name: "Bad vendor and product, bad separator",
args: []string{"vendor=13d3:product=5406"}, args: []string{"vendor=13d3:product=5406"},
result: []machine.USBConfig{}, result: []command.USBConfig{},
wantErr: true, wantErr: true,
}, },
{ {
name: "Bad vendor and product, missing equal", name: "Bad vendor and product, missing equal",
args: []string{"vendor=13d3:product-5406"}, args: []string{"vendor=13d3:product-5406"},
result: []machine.USBConfig{}, result: []command.USBConfig{},
wantErr: true, wantErr: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
got, err := parseUSBs(test.args) got, err := command.ParseUSBs(test.args)
if (err != nil) != test.wantErr { if (err != nil) != test.wantErr {
t.Errorf("parseUUBs error = %v, wantErr %v", err, test.wantErr) t.Errorf("parseUUBs error = %v, wantErr %v", err, test.wantErr)
return return

View File

@ -6,7 +6,6 @@ package qemu
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -25,6 +24,9 @@ import (
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/define" "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/rootless"
"github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/pkg/util"
"github.com/containers/storage/pkg/lockfile" "github.com/containers/storage/pkg/lockfile"
@ -66,13 +68,13 @@ type MachineVM struct {
// ConfigPath is the path to the configuration file // ConfigPath is the path to the configuration file
ConfigPath define.VMFile ConfigPath define.VMFile
// The command line representation of the qemu command // The command line representation of the qemu command
CmdLine QemuCmd CmdLine command.QemuCmd
// HostUser contains info about host user // HostUser contains info about host user
machine.HostUser vmconfigs.HostUser
// ImageConfig describes the bootable image // ImageConfig describes the bootable image
machine.ImageConfig machine.ImageConfig
// Mounts is the list of remote filesystems to mount // Mounts is the list of remote filesystems to mount
Mounts []machine.Mount Mounts []vmconfigs.Mount
// Name of VM // Name of VM
Name string Name string
// PidFilePath is the where the Proxy PID file lives // 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 is the where the VM PID file lives
VMPidFilePath define.VMFile VMPidFilePath define.VMFile
// QMPMonitor is the qemu monitor object for sending commands // QMPMonitor is the qemu monitor object for sending commands
QMPMonitor Monitor QMPMonitor command.Monitor
// ReadySocket tells host when vm is booted // ReadySocket tells host when vm is booted
ReadySocket define.VMFile ReadySocket define.VMFile
// ResourceConfig is physical attrs of the VM // ResourceConfig is physical attrs of the VM
machine.ResourceConfig vmconfigs.ResourceConfig
// SSHConfig for accessing the remote vm // 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 tells us whether the machine is running or if we have just dialed it to start it
Starting bool Starting bool
// Created contains the original created time instead of querying the file mod time // Created contains the original created time instead of querying the file mod time
@ -98,15 +100,6 @@ type MachineVM struct {
lock *lockfile.LockFile 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 // addMountsToVM converts the volumes passed through the CLI into the specified
// volume driver and adds them to the machine // volume driver and adds them to the machine
func (v *MachineVM) addMountsToVM(opts machine.InitOptions) error { 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) return fmt.Errorf("unknown volume driver: %s", opts.VolumeDriver)
} }
mounts := []machine.Mount{} mounts := []vmconfigs.Mount{}
for i, volume := range opts.Volumes { for i, volume := range opts.Volumes {
tag := fmt.Sprintf("vol%d", i) tag := fmt.Sprintf("vol%d", i)
paths := pathsFromVolume(volume) paths := pathsFromVolume(volume)
@ -128,7 +121,7 @@ func (v *MachineVM) addMountsToVM(opts machine.InitOptions) error {
readonly, securityModel := extractMountOptions(paths) readonly, securityModel := extractMountOptions(paths)
if volumeType == VolumeTypeVirtfs { if volumeType == VolumeTypeVirtfs {
v.CmdLine.SetVirtfsMount(source, tag, securityModel, readonly) 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 v.Mounts = mounts
@ -274,7 +267,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) {
return setErrors, err return setErrors, err
} }
if state == machine.Running { if state == define.Running {
suffix := "" suffix := ""
if v.Name != machine.DefaultMachineName { if v.Name != machine.DefaultMachineName {
suffix = " " + v.Name suffix = " " + v.Name
@ -309,7 +302,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) {
} }
if opts.USBs != nil { 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)) setErrors = append(setErrors, fmt.Errorf("failed to set usb: %w", err))
} else { } else {
v.USBs = usbConfigs v.USBs = usbConfigs
@ -381,7 +374,7 @@ func (v *MachineVM) conductVMReadinessCheck(name string, maxBackoffs int, backof
if err != nil { if err != nil {
return false, nil, err 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 // Also make sure that SSH is up and running. The
// ready service's dependencies don't fully make sure // ready service's dependencies don't fully make sure
// that clients can SSH into the machine immediately // that clients can SSH into the machine immediately
@ -469,9 +462,9 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error {
return err return err
} }
switch state { 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) 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) 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 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 { if err != nil {
return err return err
} }
@ -592,7 +585,7 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error {
fmt.Println("Waiting for VM ...") 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 { if err != nil {
return err return err
} }
@ -656,36 +649,7 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error {
return nil return nil
} }
// propagateHostEnv is here for providing the ability to propagate func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (define.Status, error) {
// 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) {
// this is the format returned from the monitor // this is the format returned from the monitor
// {"return": {"status": "running", "singlestep": false, "running": true}} // {"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) b, err := monitor.Run(input)
if err != nil { if err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
return machine.Stopped, nil return define.Stopped, nil
} }
return "", err return "", err
} }
if err := json.Unmarshal(b, &response); err != nil { if err := json.Unmarshal(b, &response); err != nil {
return "", err return "", err
} }
if response.Response.Status == machine.Running { if response.Response.Status == define.Running {
return machine.Running, nil return define.Running, nil
} }
return machine.Stopped, nil return define.Stopped, nil
} }
// waitForMachineToStop waits for the machine to stop running // waitForMachineToStop waits for the machine to stop running
@ -734,7 +698,7 @@ func (v *MachineVM) waitForMachineToStop() error {
if err != nil { if err != nil {
return err return err
} }
if state != machine.Running { if state != define.Running {
break break
} }
time.Sleep(waitInternal) time.Sleep(waitInternal)
@ -929,10 +893,10 @@ func (v *MachineVM) stopLocked() error {
} }
// NewQMPMonitor creates the monitor subsection of our vm // 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() rtDir, err := getRuntimeDir()
if err != nil { if err != nil {
return Monitor{}, err return command.Monitor{}, err
} }
if isRootful() { if isRootful() {
rtDir = "/run" rtDir = "/run"
@ -940,7 +904,7 @@ func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error)
rtDir = filepath.Join(rtDir, "podman") rtDir = filepath.Join(rtDir, "podman")
if _, err := os.Stat(rtDir); errors.Is(err, fs.ErrNotExist) { if _, err := os.Stat(rtDir); errors.Is(err, fs.ErrNotExist) {
if err := os.MkdirAll(rtDir, 0755); err != nil { if err := os.MkdirAll(rtDir, 0755); err != nil {
return Monitor{}, err return command.Monitor{}, err
} }
} }
if timeout == 0 { 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) address, err := define.NewMachineFile(filepath.Join(rtDir, "qmp_"+name+".sock"), nil)
if err != nil { if err != nil {
return Monitor{}, err return command.Monitor{}, err
} }
monitor := Monitor{ monitor := command.Monitor{
Network: network, Network: network,
Address: *address, Address: *address,
Timeout: timeout, Timeout: timeout,
@ -1021,7 +985,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
if state == machine.Running { if state == define.Running {
if !opts.Force { if !opts.Force {
return "", nil, &machine.ErrVMRunningCannotDestroyed{Name: v.Name} return "", nil, &machine.ErrVMRunningCannotDestroyed{Name: v.Name}
} }
@ -1050,7 +1014,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
}, nil }, 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 // Check if qmp socket path exists
if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); errors.Is(err, fs.ErrNotExist) { if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); errors.Is(err, fs.ErrNotExist) {
return "", nil return "", nil
@ -1061,7 +1025,7 @@ func (v *MachineVM) State(bypass bool) (machine.Status, error) {
} }
// Check if we can dial it // Check if we can dial it
if v.Starting && !bypass { 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) monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
if err != nil { 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 // it can appear as though the machine state is not stopped. Check for ECONNREFUSED
// almost assures us that the vm is stopped. // almost assures us that the vm is stopped.
if errors.Is(err, syscall.ECONNREFUSED) { if errors.Is(err, syscall.ECONNREFUSED) {
return machine.Stopped, nil return define.Stopped, nil
} }
return "", err return "", err
} }
@ -1102,7 +1066,7 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error {
if err != nil { if err != nil {
return err return err
} }
if state != machine.Running { if state != define.Running {
return fmt.Errorf("vm %q is not running", v.Name) return fmt.Errorf("vm %q is not running", v.Name)
} }

View File

@ -4,105 +4,18 @@
package qemu package qemu
import ( import (
"encoding/base64"
"fmt"
"strings"
"testing" "testing"
"github.com/containers/common/libnetwork/etchosts" "github.com/containers/podman/v4/pkg/machine/qemu/command"
"github.com/containers/podman/v4/pkg/machine"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestEditCmd(t *testing.T) { func TestEditCmd(t *testing.T) {
vm := new(MachineVM) vm := new(MachineVM)
vm.CmdLine = QemuCmd{"command", "-flag", "value"} vm.CmdLine = command.QemuCmd{"command", "-flag", "value"}
vm.editCmdLine("-flag", "newvalue") vm.editCmdLine("-flag", "newvalue")
vm.editCmdLine("-anotherflag", "anothervalue") vm.editCmdLine("-anotherflag", "anothervalue")
require.Equal(t, vm.CmdLine.Build(), []string{"command", "-flag", "newvalue", "-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 ( import (
"bufio" "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 ( import (
"strings" "strings"
"github.com/containers/podman/v4/pkg/machine/vmconfigs"
) )
type Volume interface { type Volume interface {
@ -37,8 +39,8 @@ func (v VirtIoFs) unitName() string {
return unit return unit
} }
func (v VirtIoFs) ToMount() Mount { func (v VirtIoFs) ToMount() vmconfigs.Mount {
return Mount{ return vmconfigs.Mount{
ReadOnly: v.ReadOnly, ReadOnly: v.ReadOnly,
Source: v.Source, Source: v.Source,
Tag: v.Tag, Tag: v.Tag,

View File

@ -20,6 +20,7 @@ import (
"github.com/containers/common/pkg/config" "github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/define" "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/machine/wsl/wutil"
"github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/pkg/util"
"github.com/containers/podman/v4/utils" "github.com/containers/podman/v4/utils"
@ -298,7 +299,7 @@ type MachineVM struct {
// Whether this machine should run in a rootful or rootless manner // Whether this machine should run in a rootful or rootless manner
Rootful bool Rootful bool
// SSH identity, username, etc // SSH identity, username, etc
machine.SSHConfig vmconfigs.SSHConfig
// machine version // machine version
Version int Version int
// Whether to use user-mode networking // Whether to use user-mode networking
@ -1526,12 +1527,12 @@ func unregisterDist(dist string) error {
return cmd.Run() return cmd.Run()
} }
func (v *MachineVM) State(bypass bool) (machine.Status, error) { func (v *MachineVM) State(bypass bool) (define.Status, error) {
if v.isRunning() { if v.isRunning() {
return machine.Running, nil return define.Running, nil
} }
return machine.Stopped, nil return define.Stopped, nil
} }
func stopWinProxy(v *MachineVM) error { func stopWinProxy(v *MachineVM) error {
@ -1808,7 +1809,7 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) {
machinePipe := toDist(v.Name) machinePipe := toDist(v.Name)
connInfo.PodmanPipe = &define.VMFile{Path: `\\.\pipe\` + machinePipe} connInfo.PodmanPipe = &define.VMFile{Path: `\\.\pipe\` + machinePipe}
created, lastUp, _ := v.updateTimeStamps(state == machine.Running) created, lastUp, _ := v.updateTimeStamps(state == define.Running)
return &machine.InspectInfo{ return &machine.InspectInfo{
ConfigPath: define.VMFile{Path: v.ConfigPath}, ConfigPath: define.VMFile{Path: v.ConfigPath},
ConnectionInfo: *connInfo, ConnectionInfo: *connInfo,
@ -1827,7 +1828,7 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) {
}, nil }, nil
} }
func (v *MachineVM) getResources() (resources machine.ResourceConfig) { func (v *MachineVM) getResources() (resources vmconfigs.ResourceConfig) {
resources.CPUs, _ = getCPUs(v) resources.CPUs, _ = getCPUs(v)
resources.Memory, _ = getMem(v) resources.Memory, _ = getMem(v)
resources.DiskSize = getDiskSize(v) resources.DiskSize = getDiskSize(v)