mirror of
https://github.com/containers/podman.git
synced 2025-05-20 00:27:03 +08:00

this is a logical place to get changes upstream before they grow out of control. this pr is the first in an effort to deduplicate machine code and streamline code flow. a lot of code is simply moved to eliminate circular imports. names and specific paths can ultimately be changed. i dont like some of the descriptive interface names, etc. ultimately, i think once we have the "old" code sanitized, we can re-use some of those. clearly some of what is in here is temporary and will either be deleted, changed, or moved again as this effort comes to a close. right now, the machine code does not use any of the "new" code. you will see in `init` and `rm` some commented out code that hooks it. i'm afraid things will get worse before they get better (way worse). [NO NEW TESTS NEEDED] Signed-off-by: Brent Baude <bbaude@redhat.com>
264 lines
7.8 KiB
Go
264 lines
7.8 KiB
Go
package command
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"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"
|
|
)
|
|
|
|
// defaultQMPTimeout is the timeout duration for the
|
|
// qmp monitor interactions.
|
|
var (
|
|
defaultQMPTimeout = 2 * time.Second
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// NewQMPMonitor creates the monitor subsection of our vm
|
|
func NewQMPMonitor(name, machineRuntimeDir string) (Monitor, error) {
|
|
if _, err := os.Stat(machineRuntimeDir); errors.Is(err, fs.ErrNotExist) {
|
|
if err := os.MkdirAll(machineRuntimeDir, 0755); err != nil {
|
|
return Monitor{}, err
|
|
}
|
|
}
|
|
address, err := define.NewMachineFile(filepath.Join(machineRuntimeDir, "qmp_"+name+".sock"), nil)
|
|
if err != nil {
|
|
return Monitor{}, err
|
|
}
|
|
monitor := Monitor{
|
|
Network: "unix",
|
|
Address: *address,
|
|
Timeout: defaultQMPTimeout,
|
|
}
|
|
return monitor, nil
|
|
}
|