mirror of
https://github.com/containers/podman.git
synced 2025-12-06 05:37:49 +08:00
883 lines
24 KiB
Go
883 lines
24 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// The VirtioDevice interface is an interface which is implemented by all virtio devices.
|
|
type VirtioDevice VMComponent
|
|
|
|
const (
|
|
// Possible values for VirtioInput.InputType
|
|
VirtioInputPointingDevice = "pointing"
|
|
VirtioInputKeyboardDevice = "keyboard"
|
|
|
|
// Options for VirtioGPUResolution
|
|
VirtioGPUResolutionWidth = "width"
|
|
VirtioGPUResolutionHeight = "height"
|
|
|
|
// Default VirtioGPU Resolution
|
|
defaultVirtioGPUResolutionWidth = 800
|
|
defaultVirtioGPUResolutionHeight = 600
|
|
)
|
|
|
|
// VirtioInput configures an input device, such as a keyboard or pointing device
|
|
// (mouse) that the virtual machine can use
|
|
type VirtioInput struct {
|
|
InputType string `json:"inputType"` // currently supports "pointing" and "keyboard" input types
|
|
}
|
|
|
|
type VirtioGPUResolution struct {
|
|
Width int `json:"width"`
|
|
Height int `json:"height"`
|
|
}
|
|
|
|
// VirtioGPU configures a GPU device, such as the host computer's display
|
|
type VirtioGPU struct {
|
|
UsesGUI bool `json:"usesGUI"`
|
|
VirtioGPUResolution
|
|
}
|
|
|
|
// VirtioVsock configures of a virtio-vsock device allowing 2-way communication
|
|
// between the host and the virtual machine type
|
|
type VirtioVsock struct {
|
|
// Port is the virtio-vsock port used for this device, see `man vsock` for more
|
|
// details.
|
|
Port uint32 `json:"port"`
|
|
// SocketURL is the path to a unix socket on the host to use for the virtio-vsock communication with the guest.
|
|
SocketURL string `json:"socketURL"`
|
|
// If true, vsock connections will have to be done from guest to host. If false, vsock connections will only be possible
|
|
// from host to guest
|
|
Listen bool `json:"listen,omitempty"`
|
|
}
|
|
|
|
// VirtioBlk configures a disk device.
|
|
type VirtioBlk struct {
|
|
DiskStorageConfig
|
|
DeviceIdentifier string `json:"deviceIdentifier,omitempty"`
|
|
}
|
|
|
|
type DirectorySharingConfig struct {
|
|
MountTag string `json:"mountTag"`
|
|
}
|
|
|
|
// VirtioFs configures directory sharing between the guest and the host.
|
|
type VirtioFs struct {
|
|
DirectorySharingConfig
|
|
SharedDir string `json:"sharedDir"`
|
|
}
|
|
|
|
// RosettaShare configures rosetta support in the guest to run Intel binaries on Apple CPUs
|
|
type RosettaShare struct {
|
|
DirectorySharingConfig
|
|
InstallRosetta bool `json:"installRosetta"`
|
|
IgnoreIfMissing bool `json:"ignoreIfMissing"`
|
|
}
|
|
|
|
// NVMExpressController configures a NVMe controller in the guest
|
|
type NVMExpressController struct {
|
|
DiskStorageConfig
|
|
}
|
|
|
|
// VirtioRng configures a random number generator (RNG) device.
|
|
type VirtioRng struct {
|
|
}
|
|
|
|
// TODO: Add BridgedNetwork support
|
|
// https://github.com/Code-Hex/vz/blob/d70a0533bf8ed0fa9ab22fa4d4ca554b7c3f3ce5/network.go#L81-L82
|
|
|
|
// VirtioNet configures the virtual machine networking.
|
|
type VirtioNet struct {
|
|
Nat bool `json:"nat"`
|
|
MacAddress net.HardwareAddr `json:"-"` // custom marshaller in json.go
|
|
// file parameter is holding a connected datagram socket.
|
|
// see https://github.com/Code-Hex/vz/blob/7f648b6fb9205d6f11792263d79876e3042c33ec/network.go#L113-L155
|
|
Socket *os.File `json:"socket,omitempty"`
|
|
|
|
UnixSocketPath string `json:"unixSocketPath,omitempty"`
|
|
}
|
|
|
|
// VirtioSerial configures the virtual machine serial ports.
|
|
type VirtioSerial struct {
|
|
LogFile string `json:"logFile,omitempty"`
|
|
UsesStdio bool `json:"usesStdio,omitempty"`
|
|
}
|
|
|
|
type NBDSynchronizationMode string
|
|
|
|
const (
|
|
SynchronizationFullMode NBDSynchronizationMode = "full"
|
|
SynchronizationNoneMode NBDSynchronizationMode = "none"
|
|
)
|
|
|
|
type NetworkBlockDevice struct {
|
|
NetworkBlockStorageConfig
|
|
DeviceIdentifier string
|
|
Timeout time.Duration
|
|
SynchronizationMode NBDSynchronizationMode
|
|
}
|
|
|
|
type VirtioBalloon struct{}
|
|
|
|
func VirtioBalloonNew() (VirtioDevice, error) {
|
|
return &VirtioBalloon{}, nil
|
|
}
|
|
|
|
func (v *VirtioBalloon) FromOptions(options []option) error {
|
|
if len(options) != 0 {
|
|
return fmt.Errorf("unknown options for virtio-balloon devices: %s", options)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *VirtioBalloon) ToCmdLine() ([]string, error) {
|
|
return []string{"--device", "virtio-balloon"}, nil
|
|
}
|
|
|
|
type option struct {
|
|
key string
|
|
value string
|
|
}
|
|
|
|
func strToOption(str string) option {
|
|
splitStr := strings.SplitN(str, "=", 2)
|
|
|
|
opt := option{
|
|
key: splitStr[0],
|
|
}
|
|
if len(splitStr) > 1 {
|
|
opt.value = splitStr[1]
|
|
}
|
|
|
|
return opt
|
|
}
|
|
|
|
func strvToOptions(opts []string) []option {
|
|
parsedOpts := []option{}
|
|
for _, opt := range opts {
|
|
if len(opt) == 0 {
|
|
continue
|
|
}
|
|
parsedOpts = append(parsedOpts, strToOption(opt))
|
|
}
|
|
|
|
return parsedOpts
|
|
}
|
|
|
|
func deviceFromCmdLine(deviceOpts string) (VirtioDevice, error) {
|
|
opts := strings.Split(deviceOpts, ",")
|
|
if len(opts) == 0 {
|
|
return nil, fmt.Errorf("empty option list in command line argument")
|
|
}
|
|
var dev VirtioDevice
|
|
switch opts[0] {
|
|
case "rosetta":
|
|
dev = &RosettaShare{}
|
|
case "nvme":
|
|
dev = nvmExpressControllerNewEmpty()
|
|
case "virtio-blk":
|
|
dev = virtioBlkNewEmpty()
|
|
case "virtio-fs":
|
|
dev = &VirtioFs{}
|
|
case "virtio-net":
|
|
dev = &VirtioNet{}
|
|
case "virtio-rng":
|
|
dev = &VirtioRng{}
|
|
case "virtio-serial":
|
|
dev = &VirtioSerial{}
|
|
case "virtio-vsock":
|
|
dev = &VirtioVsock{}
|
|
case "usb-mass-storage":
|
|
dev = usbMassStorageNewEmpty()
|
|
case "virtio-input":
|
|
dev = &VirtioInput{}
|
|
case "virtio-gpu":
|
|
dev = &VirtioGPU{}
|
|
case "virtio-balloon":
|
|
dev = &VirtioBalloon{}
|
|
case "nbd":
|
|
dev = networkBlockDeviceNewEmpty()
|
|
default:
|
|
return nil, fmt.Errorf("unknown device type: %s", opts[0])
|
|
}
|
|
|
|
parsedOpts := strvToOptions(opts[1:])
|
|
if err := dev.FromOptions(parsedOpts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return dev, nil
|
|
}
|
|
|
|
// VirtioSerialNew creates a new serial device for the virtual machine. The
|
|
// output the virtual machine sent to the serial port will be written to the
|
|
// file at logFilePath.
|
|
func VirtioSerialNew(logFilePath string) (VirtioDevice, error) {
|
|
return &VirtioSerial{
|
|
LogFile: logFilePath,
|
|
}, nil
|
|
}
|
|
|
|
func VirtioSerialNewStdio() (VirtioDevice, error) {
|
|
return &VirtioSerial{
|
|
UsesStdio: true,
|
|
}, nil
|
|
}
|
|
|
|
func (dev *VirtioSerial) validate() error {
|
|
if dev.LogFile != "" && dev.UsesStdio {
|
|
return fmt.Errorf("'logFilePath' and 'stdio' cannot be set at the same time")
|
|
}
|
|
if dev.LogFile == "" && !dev.UsesStdio {
|
|
return fmt.Errorf("one of 'logFilePath' or 'stdio' must be set")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (dev *VirtioSerial) ToCmdLine() ([]string, error) {
|
|
if err := dev.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
if dev.UsesStdio {
|
|
return []string{"--device", "virtio-serial,stdio"}, nil
|
|
}
|
|
|
|
return []string{"--device", fmt.Sprintf("virtio-serial,logFilePath=%s", dev.LogFile)}, nil
|
|
}
|
|
|
|
func (dev *VirtioSerial) FromOptions(options []option) error {
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case "logFilePath":
|
|
dev.LogFile = option.value
|
|
case "stdio":
|
|
if option.value != "" {
|
|
return fmt.Errorf("unexpected value for virtio-serial 'stdio' option: %s", option.value)
|
|
}
|
|
dev.UsesStdio = true
|
|
default:
|
|
return fmt.Errorf("unknown option for virtio-serial devices: %s", option.key)
|
|
}
|
|
}
|
|
|
|
return dev.validate()
|
|
}
|
|
|
|
// VirtioInputNew creates a new input device for the virtual machine.
|
|
// The inputType parameter is the type of virtio-input device that will be added
|
|
// to the machine.
|
|
func VirtioInputNew(inputType string) (VirtioDevice, error) {
|
|
dev := &VirtioInput{
|
|
InputType: inputType,
|
|
}
|
|
if err := dev.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return dev, nil
|
|
}
|
|
|
|
func (dev *VirtioInput) validate() error {
|
|
if dev.InputType != VirtioInputPointingDevice && dev.InputType != VirtioInputKeyboardDevice {
|
|
return fmt.Errorf("unknown option for virtio-input devices: %s", dev.InputType)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (dev *VirtioInput) ToCmdLine() ([]string, error) {
|
|
if err := dev.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []string{"--device", fmt.Sprintf("virtio-input,%s", dev.InputType)}, nil
|
|
}
|
|
|
|
func (dev *VirtioInput) FromOptions(options []option) error {
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case VirtioInputPointingDevice, VirtioInputKeyboardDevice:
|
|
if option.value != "" {
|
|
return fmt.Errorf("unexpected value for virtio-input %s option: %s", option.key, option.value)
|
|
}
|
|
dev.InputType = option.key
|
|
default:
|
|
return fmt.Errorf("unknown option for virtio-input devices: %s", option.key)
|
|
}
|
|
}
|
|
return dev.validate()
|
|
}
|
|
|
|
// VirtioGPUNew creates a new gpu device for the virtual machine.
|
|
// The usesGUI parameter determines whether a graphical application window will
|
|
// be displayed
|
|
func VirtioGPUNew() (VirtioDevice, error) {
|
|
return &VirtioGPU{
|
|
UsesGUI: false,
|
|
VirtioGPUResolution: VirtioGPUResolution{
|
|
Width: defaultVirtioGPUResolutionWidth,
|
|
Height: defaultVirtioGPUResolutionHeight,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (dev *VirtioGPU) validate() error {
|
|
if dev.Height < 1 || dev.Width < 1 {
|
|
return fmt.Errorf("invalid dimensions for virtio-gpu device resolution: %dx%d", dev.Width, dev.Height)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (dev *VirtioGPU) ToCmdLine() ([]string, error) {
|
|
if err := dev.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []string{"--device", fmt.Sprintf("virtio-gpu,width=%d,height=%d", dev.Width, dev.Height)}, nil
|
|
}
|
|
|
|
func (dev *VirtioGPU) FromOptions(options []option) error {
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case VirtioGPUResolutionHeight:
|
|
height, err := strconv.Atoi(option.value)
|
|
if err != nil || height < 1 {
|
|
return fmt.Errorf("Invalid value for virtio-gpu %s: %s", option.key, option.value)
|
|
}
|
|
|
|
dev.Height = height
|
|
case VirtioGPUResolutionWidth:
|
|
width, err := strconv.Atoi(option.value)
|
|
if err != nil || width < 1 {
|
|
return fmt.Errorf("Invalid value for virtio-gpu %s: %s", option.key, option.value)
|
|
}
|
|
|
|
dev.Width = width
|
|
default:
|
|
return fmt.Errorf("unknown option for virtio-gpu devices: %s", option.key)
|
|
}
|
|
}
|
|
|
|
if dev.Width == 0 && dev.Height == 0 {
|
|
dev.Width = defaultVirtioGPUResolutionWidth
|
|
dev.Height = defaultVirtioGPUResolutionHeight
|
|
}
|
|
|
|
return dev.validate()
|
|
}
|
|
|
|
// VirtioNetNew creates a new network device for the virtual machine. It will
|
|
// use macAddress as its MAC address.
|
|
func VirtioNetNew(macAddress string) (*VirtioNet, error) {
|
|
var hwAddr net.HardwareAddr
|
|
|
|
if macAddress != "" {
|
|
var err error
|
|
if hwAddr, err = net.ParseMAC(macAddress); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &VirtioNet{
|
|
Nat: true,
|
|
MacAddress: hwAddr,
|
|
}, nil
|
|
}
|
|
|
|
// SetSocket Set the socket to use for the network communication
|
|
//
|
|
// This maps the virtual machine network interface to a connected datagram
|
|
// socket. This means all network traffic on this interface will go through
|
|
// file.
|
|
// file must be a connected datagram (SOCK_DGRAM) socket.
|
|
func (dev *VirtioNet) SetSocket(file *os.File) {
|
|
dev.Socket = file
|
|
dev.Nat = false
|
|
}
|
|
|
|
func (dev *VirtioNet) SetUnixSocketPath(path string) {
|
|
dev.UnixSocketPath = path
|
|
dev.Nat = false
|
|
}
|
|
|
|
func (dev *VirtioNet) validate() error {
|
|
if dev.Nat && dev.Socket != nil {
|
|
return fmt.Errorf("'nat' and 'fd' cannot be set at the same time")
|
|
}
|
|
if dev.Nat && dev.UnixSocketPath != "" {
|
|
return fmt.Errorf("'nat' and 'unixSocketPath' cannot be set at the same time")
|
|
}
|
|
if dev.Socket != nil && dev.UnixSocketPath != "" {
|
|
return fmt.Errorf("'fd' and 'unixSocketPath' cannot be set at the same time")
|
|
}
|
|
if !dev.Nat && dev.Socket == nil && dev.UnixSocketPath == "" {
|
|
return fmt.Errorf("one of 'nat' or 'fd' or 'unixSocketPath' must be set")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (dev *VirtioNet) ToCmdLine() ([]string, error) {
|
|
if err := dev.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
builder := strings.Builder{}
|
|
builder.WriteString("virtio-net")
|
|
switch {
|
|
case dev.Nat:
|
|
builder.WriteString(",nat")
|
|
case dev.UnixSocketPath != "":
|
|
fmt.Fprintf(&builder, ",unixSocketPath=%s", dev.UnixSocketPath)
|
|
default:
|
|
fmt.Fprintf(&builder, ",fd=%d", dev.Socket.Fd())
|
|
}
|
|
|
|
if len(dev.MacAddress) != 0 {
|
|
builder.WriteString(fmt.Sprintf(",mac=%s", dev.MacAddress))
|
|
}
|
|
|
|
return []string{"--device", builder.String()}, nil
|
|
}
|
|
|
|
func (dev *VirtioNet) FromOptions(options []option) error {
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case "nat":
|
|
if option.value != "" {
|
|
return fmt.Errorf("unexpected value for virtio-net 'nat' option: %s", option.value)
|
|
}
|
|
dev.Nat = true
|
|
case "mac":
|
|
macAddress, err := net.ParseMAC(option.value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dev.MacAddress = macAddress
|
|
case "fd":
|
|
fd, err := strconv.Atoi(option.value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dev.Socket = os.NewFile(uintptr(fd), "vfkit virtio-net socket")
|
|
case "unixSocketPath":
|
|
dev.UnixSocketPath = option.value
|
|
default:
|
|
return fmt.Errorf("unknown option for virtio-net devices: %s", option.key)
|
|
}
|
|
}
|
|
|
|
return dev.validate()
|
|
}
|
|
|
|
// VirtioRngNew creates a new random number generator device to feed entropy
|
|
// into the virtual machine.
|
|
func VirtioRngNew() (VirtioDevice, error) {
|
|
return &VirtioRng{}, nil
|
|
}
|
|
|
|
func (dev *VirtioRng) ToCmdLine() ([]string, error) {
|
|
return []string{"--device", "virtio-rng"}, nil
|
|
}
|
|
|
|
func (dev *VirtioRng) FromOptions(options []option) error {
|
|
if len(options) != 0 {
|
|
return fmt.Errorf("unknown options for virtio-rng devices: %s", options)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func nvmExpressControllerNewEmpty() *NVMExpressController {
|
|
return &NVMExpressController{
|
|
DiskStorageConfig: DiskStorageConfig{
|
|
StorageConfig: StorageConfig{
|
|
DevName: "nvme",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NVMExpressControllerNew creates a new NVMExpress controller to use in the
|
|
// virtual machine. It will use the file at imagePath as the disk image. This
|
|
// image must be in raw format.
|
|
func NVMExpressControllerNew(imagePath string) (*NVMExpressController, error) {
|
|
r := nvmExpressControllerNewEmpty()
|
|
r.ImagePath = imagePath
|
|
return r, nil
|
|
}
|
|
|
|
func virtioBlkNewEmpty() *VirtioBlk {
|
|
return &VirtioBlk{
|
|
DiskStorageConfig: DiskStorageConfig{
|
|
StorageConfig: StorageConfig{
|
|
DevName: "virtio-blk",
|
|
},
|
|
},
|
|
DeviceIdentifier: "",
|
|
}
|
|
}
|
|
|
|
// VirtioBlkNew creates a new disk to use in the virtual machine. It will use
|
|
// the file at imagePath as the disk image. This image must be in raw format.
|
|
func VirtioBlkNew(imagePath string) (*VirtioBlk, error) {
|
|
virtioBlk := virtioBlkNewEmpty()
|
|
virtioBlk.ImagePath = imagePath
|
|
|
|
return virtioBlk, nil
|
|
}
|
|
|
|
func (dev *VirtioBlk) SetDeviceIdentifier(devID string) {
|
|
dev.DeviceIdentifier = devID
|
|
}
|
|
|
|
func (dev *VirtioBlk) FromOptions(options []option) error {
|
|
unhandledOpts := []option{}
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case "deviceId":
|
|
dev.DeviceIdentifier = option.value
|
|
default:
|
|
unhandledOpts = append(unhandledOpts, option)
|
|
}
|
|
}
|
|
|
|
return dev.DiskStorageConfig.FromOptions(unhandledOpts)
|
|
}
|
|
|
|
func (dev *VirtioBlk) ToCmdLine() ([]string, error) {
|
|
cmdLine, err := dev.DiskStorageConfig.ToCmdLine()
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
if len(cmdLine) != 2 {
|
|
return []string{}, fmt.Errorf("unexpected storage config commandline")
|
|
}
|
|
if dev.DeviceIdentifier != "" {
|
|
cmdLine[1] = fmt.Sprintf("%s,deviceId=%s", cmdLine[1], dev.DeviceIdentifier)
|
|
}
|
|
return cmdLine, nil
|
|
}
|
|
|
|
// VirtioVsockNew creates a new virtio-vsock device for 2-way communication
|
|
// between the host and the virtual machine. The communication will happen on
|
|
// vsock port, and on the host it will use the unix socket at socketURL.
|
|
// When listen is true, the host will be listening for connections over vsock.
|
|
// When listen is false, the guest will be listening for connections over vsock.
|
|
func VirtioVsockNew(port uint, socketURL string, listen bool) (VirtioDevice, error) {
|
|
if port > math.MaxUint32 {
|
|
return nil, fmt.Errorf("invalid vsock port: %d", port)
|
|
}
|
|
return &VirtioVsock{
|
|
Port: uint32(port), //#nosec G115 -- was compared to math.MaxUint32
|
|
SocketURL: socketURL,
|
|
Listen: listen,
|
|
}, nil
|
|
}
|
|
|
|
func (dev *VirtioVsock) ToCmdLine() ([]string, error) {
|
|
if dev.Port == 0 || dev.SocketURL == "" {
|
|
return nil, fmt.Errorf("virtio-vsock needs both a port and a socket URL")
|
|
}
|
|
var listenStr string
|
|
if dev.Listen {
|
|
listenStr = "listen"
|
|
} else {
|
|
listenStr = "connect"
|
|
}
|
|
return []string{"--device", fmt.Sprintf("virtio-vsock,port=%d,socketURL=%s,%s", dev.Port, dev.SocketURL, listenStr)}, nil
|
|
}
|
|
|
|
func (dev *VirtioVsock) FromOptions(options []option) error {
|
|
// default to listen for backwards compatibliity
|
|
dev.Listen = true
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case "socketURL":
|
|
dev.SocketURL = option.value
|
|
case "port":
|
|
port, err := strconv.ParseUint(option.value, 10, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dev.Port = uint32(port) //#nosec G115 -- ParseUint(_, _, 32) guarantees no overflow
|
|
case "listen":
|
|
dev.Listen = true
|
|
case "connect":
|
|
dev.Listen = false
|
|
default:
|
|
return fmt.Errorf("unknown option for virtio-vsock devices: %s", option.key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// VirtioFsNew creates a new virtio-fs device for file sharing. It will share
|
|
// the directory at sharedDir with the virtual machine. This directory can be
|
|
// mounted in the VM using `mount -t virtiofs mountTag /some/dir`
|
|
func VirtioFsNew(sharedDir string, mountTag string) (VirtioDevice, error) {
|
|
return &VirtioFs{
|
|
DirectorySharingConfig: DirectorySharingConfig{
|
|
MountTag: mountTag,
|
|
},
|
|
SharedDir: sharedDir,
|
|
}, nil
|
|
}
|
|
|
|
func (dev *VirtioFs) ToCmdLine() ([]string, error) {
|
|
if dev.SharedDir == "" {
|
|
return nil, fmt.Errorf("virtio-fs needs the path to the directory to share")
|
|
}
|
|
if dev.MountTag != "" {
|
|
return []string{"--device", fmt.Sprintf("virtio-fs,sharedDir=%s,mountTag=%s", dev.SharedDir, dev.MountTag)}, nil
|
|
}
|
|
|
|
return []string{"--device", fmt.Sprintf("virtio-fs,sharedDir=%s", dev.SharedDir)}, nil
|
|
}
|
|
|
|
func (dev *VirtioFs) FromOptions(options []option) error {
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case "sharedDir":
|
|
dev.SharedDir = option.value
|
|
case "mountTag":
|
|
dev.MountTag = option.value
|
|
default:
|
|
return fmt.Errorf("unknown option for virtio-fs devices: %s", option.key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RosettaShareNew RosettaShare creates a new rosetta share for running x86_64 binaries on M1 machines.
|
|
// It will share a directory containing the linux rosetta binaries with the
|
|
// virtual machine. This directory can be mounted in the VM using `mount -t
|
|
// virtiofs mountTag /some/dir`
|
|
func RosettaShareNew(mountTag string) (VirtioDevice, error) {
|
|
return &RosettaShare{
|
|
DirectorySharingConfig: DirectorySharingConfig{
|
|
MountTag: mountTag,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (dev *RosettaShare) ToCmdLine() ([]string, error) {
|
|
if dev.MountTag == "" {
|
|
return nil, fmt.Errorf("rosetta shares require a mount tag to be specified")
|
|
}
|
|
builder := strings.Builder{}
|
|
builder.WriteString("rosetta")
|
|
fmt.Fprintf(&builder, ",mountTag=%s", dev.MountTag)
|
|
if dev.InstallRosetta {
|
|
builder.WriteString(",install")
|
|
}
|
|
if dev.IgnoreIfMissing {
|
|
builder.WriteString(",ignore-if-missing")
|
|
}
|
|
|
|
return []string{"--device", builder.String()}, nil
|
|
}
|
|
|
|
func (dev *RosettaShare) FromOptions(options []option) error {
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case "mountTag":
|
|
dev.MountTag = option.value
|
|
case "install":
|
|
dev.InstallRosetta = true
|
|
case "ignore-if-missing":
|
|
dev.IgnoreIfMissing = true
|
|
default:
|
|
return fmt.Errorf("unknown option for rosetta share: %s", option.key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func networkBlockDeviceNewEmpty() *NetworkBlockDevice {
|
|
return &NetworkBlockDevice{
|
|
NetworkBlockStorageConfig: NetworkBlockStorageConfig{
|
|
StorageConfig: StorageConfig{
|
|
DevName: "nbd",
|
|
},
|
|
},
|
|
DeviceIdentifier: "",
|
|
Timeout: time.Duration(15000 * time.Millisecond), // set a default timeout to 15s
|
|
SynchronizationMode: SynchronizationFullMode, // default mode to full
|
|
}
|
|
}
|
|
|
|
// NetworkBlockDeviceNew creates a new disk by connecting to a remote Network Block Device (NBD) server.
|
|
// The provided uri must be in the format <scheme>://<address>/<export-name>
|
|
// where scheme could have any of these value: nbd, nbds, nbd+unix and nbds+unix.
|
|
// More info can be found at https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md
|
|
// This allows the virtual machine to access and use the remote storage as if it were a local disk.
|
|
func NetworkBlockDeviceNew(uri string, timeout uint32, synchronization NBDSynchronizationMode) (*NetworkBlockDevice, error) {
|
|
nbd := networkBlockDeviceNewEmpty()
|
|
nbd.URI = uri
|
|
nbd.Timeout = time.Duration(timeout) * time.Millisecond
|
|
nbd.SynchronizationMode = synchronization
|
|
|
|
return nbd, nil
|
|
}
|
|
|
|
func (nbd *NetworkBlockDevice) ToCmdLine() ([]string, error) {
|
|
cmdLine, err := nbd.NetworkBlockStorageConfig.ToCmdLine()
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
if len(cmdLine) != 2 {
|
|
return []string{}, fmt.Errorf("unexpected storage config commandline")
|
|
}
|
|
|
|
if nbd.DeviceIdentifier != "" {
|
|
cmdLine[1] = fmt.Sprintf("%s,deviceId=%s", cmdLine[1], nbd.DeviceIdentifier)
|
|
}
|
|
if nbd.Timeout.Milliseconds() > 0 {
|
|
cmdLine[1] = fmt.Sprintf("%s,timeout=%d", cmdLine[1], nbd.Timeout.Milliseconds())
|
|
}
|
|
if nbd.SynchronizationMode == "none" || nbd.SynchronizationMode == "full" {
|
|
cmdLine[1] = fmt.Sprintf("%s,sync=%s", cmdLine[1], nbd.SynchronizationMode)
|
|
}
|
|
|
|
return cmdLine, nil
|
|
}
|
|
|
|
func (nbd *NetworkBlockDevice) FromOptions(options []option) error {
|
|
unhandledOpts := []option{}
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case "deviceId":
|
|
nbd.DeviceIdentifier = option.value
|
|
case "timeout":
|
|
timeoutMS, err := strconv.ParseInt(option.value, 10, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nbd.Timeout = time.Duration(timeoutMS) * time.Millisecond
|
|
case "sync":
|
|
switch option.value {
|
|
case string(SynchronizationFullMode):
|
|
nbd.SynchronizationMode = SynchronizationFullMode
|
|
case string(SynchronizationNoneMode):
|
|
nbd.SynchronizationMode = SynchronizationNoneMode
|
|
default:
|
|
return fmt.Errorf("invalid sync mode: %s, must be 'full' or 'none'", option.value)
|
|
}
|
|
default:
|
|
unhandledOpts = append(unhandledOpts, option)
|
|
}
|
|
}
|
|
|
|
return nbd.NetworkBlockStorageConfig.FromOptions(unhandledOpts)
|
|
}
|
|
|
|
type USBMassStorage struct {
|
|
DiskStorageConfig
|
|
}
|
|
|
|
func usbMassStorageNewEmpty() *USBMassStorage {
|
|
return &USBMassStorage{
|
|
DiskStorageConfig: DiskStorageConfig{
|
|
StorageConfig: StorageConfig{
|
|
DevName: "usb-mass-storage",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// USBMassStorageNew creates a new USB disk to use in the virtual machine. It will use
|
|
// the file at imagePath as the disk image. This image must be in raw or ISO format.
|
|
func USBMassStorageNew(imagePath string) (*USBMassStorage, error) {
|
|
usbMassStorage := usbMassStorageNewEmpty()
|
|
usbMassStorage.ImagePath = imagePath
|
|
|
|
return usbMassStorage, nil
|
|
}
|
|
|
|
func (dev *USBMassStorage) SetReadOnly(readOnly bool) {
|
|
dev.StorageConfig.ReadOnly = readOnly
|
|
}
|
|
|
|
// StorageConfig configures a disk device.
|
|
type StorageConfig struct {
|
|
DevName string `json:"devName"`
|
|
ReadOnly bool `json:"readOnly,omitempty"`
|
|
}
|
|
|
|
type DiskStorageConfig struct {
|
|
StorageConfig
|
|
ImagePath string `json:"imagePath,omitempty"`
|
|
}
|
|
|
|
type NetworkBlockStorageConfig struct {
|
|
StorageConfig
|
|
URI string `json:"uri,omitempty"`
|
|
}
|
|
|
|
func (config *DiskStorageConfig) ToCmdLine() ([]string, error) {
|
|
if config.ImagePath == "" {
|
|
return nil, fmt.Errorf("%s devices need the path to a disk image", config.DevName)
|
|
}
|
|
|
|
value := fmt.Sprintf("%s,path=%s", config.DevName, config.ImagePath)
|
|
|
|
if config.ReadOnly {
|
|
value += ",readonly"
|
|
}
|
|
return []string{"--device", value}, nil
|
|
}
|
|
|
|
func (config *DiskStorageConfig) FromOptions(options []option) error {
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case "path":
|
|
config.ImagePath = option.value
|
|
case "readonly":
|
|
if option.value != "" {
|
|
return fmt.Errorf("unexpected value for virtio-blk 'readonly' option: %s", option.value)
|
|
}
|
|
config.ReadOnly = true
|
|
default:
|
|
return fmt.Errorf("unknown option for %s devices: %s", config.DevName, option.key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (config *NetworkBlockStorageConfig) ToCmdLine() ([]string, error) {
|
|
if config.URI == "" {
|
|
return nil, fmt.Errorf("%s devices need the uri to a remote block device", config.DevName)
|
|
}
|
|
|
|
value := fmt.Sprintf("%s,uri=%s", config.DevName, config.URI)
|
|
|
|
if config.ReadOnly {
|
|
value += ",readonly"
|
|
}
|
|
return []string{"--device", value}, nil
|
|
}
|
|
|
|
func (config *NetworkBlockStorageConfig) FromOptions(options []option) error {
|
|
for _, option := range options {
|
|
switch option.key {
|
|
case "uri":
|
|
config.URI = option.value
|
|
case "readonly":
|
|
if option.value != "" {
|
|
return fmt.Errorf("unexpected value for virtio-blk 'readonly' option: %s", option.value)
|
|
}
|
|
config.ReadOnly = true
|
|
default:
|
|
return fmt.Errorf("unknown option for %s devices: %s", config.DevName, option.key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|