AppleHV enablement pass #2

* Use vfkit command line assembly
* Inject ignition file into guest using http over vsock
* Ready notification through use of vsock

[NO NEW TESTS NEEDED]

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2023-05-30 07:46:18 -05:00
parent e6dbb422ef
commit 4e96686e9d
628 changed files with 267728 additions and 168 deletions

53
vendor/github.com/crc-org/vfkit/pkg/cmdline/cmdline.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
package cmdline
import (
"github.com/spf13/cobra"
)
type Options struct {
Vcpus uint
MemoryMiB uint
VmlinuzPath string
KernelCmdline string
InitrdPath string
Bootloader stringSliceValue
TimeSync string
Devices []string
RestfulURI string
LogLevel string
UseGUI bool
}
const DefaultRestfulURI = "none://"
func AddFlags(cmd *cobra.Command, opts *Options) {
cmd.Flags().StringVarP(&opts.VmlinuzPath, "kernel", "k", "", "path to the virtual machine linux kernel")
cmd.Flags().StringVarP(&opts.KernelCmdline, "kernel-cmdline", "C", "", "linux kernel command line")
cmd.Flags().StringVarP(&opts.InitrdPath, "initrd", "i", "", "path to the virtual machine initrd")
cmd.Flags().VarP(&opts.Bootloader, "bootloader", "b", "bootloader configuration")
cmd.Flags().BoolVar(&opts.UseGUI, "gui", false, "display the contents of the virtual machine onto a graphical user interface")
cmd.MarkFlagsMutuallyExclusive("kernel", "bootloader")
cmd.MarkFlagsMutuallyExclusive("initrd", "bootloader")
cmd.MarkFlagsMutuallyExclusive("kernel-cmdline", "bootloader")
cmd.MarkFlagsRequiredTogether("kernel", "initrd", "kernel-cmdline")
cmd.Flags().UintVarP(&opts.Vcpus, "cpus", "c", 1, "number of virtual CPUs")
// FIXME: use go-units for parsing
cmd.Flags().UintVarP(&opts.MemoryMiB, "memory", "m", 512, "virtual machine RAM size in mibibytes")
cmd.Flags().StringVarP(&opts.TimeSync, "timesync", "t", "", "sync guest time when host wakes up from sleep")
cmd.Flags().StringArrayVarP(&opts.Devices, "device", "d", []string{}, "devices")
cmd.Flags().StringVar(&opts.LogLevel, "log-level", "", "set log level")
cmd.Flags().StringVar(&opts.RestfulURI, "restful-uri", DefaultRestfulURI, "URI address for RestFul services")
}

View File

@@ -0,0 +1,128 @@
package cmdline
import (
"fmt"
"strings"
"github.com/crc-org/vfkit/pkg/util"
)
// -- stringSlice Value
type stringSliceValue struct {
value []string
changed bool
}
type strvBuilder struct {
strBuilder strings.Builder
strv []string
err error
}
func (builder *strvBuilder) String() string {
str := builder.strBuilder.String()
// "a","b" is parsed as { "a", "b" } to match pflag StringSlice behaviour
return util.TrimQuotes(str)
}
func (builder *strvBuilder) Next() {
str := builder.String()
builder.strv = append(builder.strv, str)
builder.strBuilder.Reset()
}
func (builder *strvBuilder) WriteRune(r rune) {
if builder.err != nil {
return
}
_, err := builder.strBuilder.WriteRune(r)
if err != nil {
builder.err = err
}
}
func (builder *strvBuilder) End() ([]string, error) {
if builder.err != nil {
return nil, builder.err
}
lastStr := builder.String()
if len(builder.strv) != 0 || len(lastStr) != 0 {
builder.strv = append(builder.strv, lastStr)
}
return builder.strv, nil
}
func parseString(str string) ([]string, error) {
withinQuotes := false
// trim spaces from str
builder := strvBuilder{}
for _, c := range str {
if withinQuotes {
if c == '"' {
withinQuotes = false
}
builder.WriteRune(c)
continue
}
if withinQuotes {
return nil, fmt.Errorf("Coding error in arg parsing")
}
switch c {
case ',':
builder.Next()
case '"':
withinQuotes = true
fallthrough
// FIXME: can we get ' ' at this point??
default:
builder.WriteRune(c)
}
}
if withinQuotes {
return nil, fmt.Errorf("Mismatched \"")
}
return builder.End()
}
func (s *stringSliceValue) Set(val string) error {
v, err := parseString(val)
if err != nil {
return err
}
if !s.changed {
s.value = v
} else {
s.value = append(s.value, v...)
}
s.changed = true
return nil
}
func (s *stringSliceValue) Type() string {
return "stringSlice"
}
func (s *stringSliceValue) String() string {
if s == nil || s.value == nil {
return "[]"
}
return "[" + strings.Join(s.value, ",") + "]"
}
func (s *stringSliceValue) Append(val string) error {
s.value = append(s.value, val)
return nil
}
func (s *stringSliceValue) Replace(val []string) error {
s.value = val
return nil
}
func (s *stringSliceValue) GetSlice() []string {
return s.value
}

View File

@@ -0,0 +1,143 @@
package config
import (
"fmt"
"strings"
"github.com/crc-org/vfkit/pkg/util"
)
// Bootloader is the base interface for all bootloader classes. It specifies how to
// boot the virtual machine. It is mandatory to set a Bootloader or the virtual
// machine won't start.
type Bootloader interface {
FromOptions(options []option) error
ToCmdLine() ([]string, error)
}
// LinuxBootloader determines which kernel/initrd/kernel args to use when starting
// the virtual machine.
type LinuxBootloader struct {
VmlinuzPath string
KernelCmdLine string
InitrdPath string
}
// EFIBootloader allows to set a few options related to EFI variable storage
type EFIBootloader struct {
EFIVariableStorePath string
// TODO: virtualization framework allow both create and overwrite
CreateVariableStore bool
}
// NewLinuxBootloader creates a new bootloader to start a VM with the file at
// vmlinuzPath as the kernel, kernelCmdLine as the kernel command line, and the
// file at initrdPath as the initrd. On ARM64, the kernel must be uncompressed
// otherwise the VM will fail to boot.
func NewLinuxBootloader(vmlinuzPath, kernelCmdLine, initrdPath string) *LinuxBootloader {
return &LinuxBootloader{
VmlinuzPath: vmlinuzPath,
KernelCmdLine: kernelCmdLine,
InitrdPath: initrdPath,
}
}
func (bootloader *LinuxBootloader) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "kernel":
bootloader.VmlinuzPath = option.value
case "cmdline":
bootloader.KernelCmdLine = util.TrimQuotes(option.value)
case "initrd":
bootloader.InitrdPath = option.value
default:
return fmt.Errorf("Unknown option for linux bootloaders: %s", option.key)
}
}
return nil
}
func (bootloader *LinuxBootloader) ToCmdLine() ([]string, error) {
args := []string{}
if bootloader.VmlinuzPath == "" {
return nil, fmt.Errorf("Missing kernel path")
}
args = append(args, "--kernel", bootloader.VmlinuzPath)
if bootloader.InitrdPath == "" {
return nil, fmt.Errorf("Missing initrd path")
}
args = append(args, "--initrd", bootloader.InitrdPath)
if bootloader.KernelCmdLine == "" {
return nil, fmt.Errorf("Missing kernel command line")
}
args = append(args, "--kernel-cmdline", bootloader.KernelCmdLine)
return args, nil
}
// NewEFIBootloader creates a new bootloader to start a VM using EFI
// efiVariableStorePath is the path to a file for EFI storage
// create is a boolean indicating if the file for the store should be created or not
func NewEFIBootloader(efiVariableStorePath string, createVariableStore bool) *EFIBootloader {
return &EFIBootloader{
EFIVariableStorePath: efiVariableStorePath,
CreateVariableStore: createVariableStore,
}
}
func (bootloader *EFIBootloader) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "variable-store":
bootloader.EFIVariableStorePath = option.value
case "create":
if option.value != "" {
return fmt.Errorf("Unexpected value for EFI bootloader 'create' option: %s", option.value)
}
bootloader.CreateVariableStore = true
default:
return fmt.Errorf("Unknown option for EFI bootloaders: %s", option.key)
}
}
return nil
}
func (bootloader *EFIBootloader) ToCmdLine() ([]string, error) {
if bootloader.EFIVariableStorePath == "" {
return nil, fmt.Errorf("Missing EFI store path")
}
builder := strings.Builder{}
builder.WriteString("efi")
builder.WriteString(fmt.Sprintf(",variable-store=%s", bootloader.EFIVariableStorePath))
if bootloader.CreateVariableStore {
builder.WriteString(",create")
}
return []string{"--bootloader", builder.String()}, nil
}
func BootloaderFromCmdLine(optsStrv []string) (Bootloader, error) {
var bootloader Bootloader
if len(optsStrv) < 1 {
return nil, fmt.Errorf("empty option list in --bootloader command line argument")
}
bootloaderType := optsStrv[0]
switch bootloaderType {
case "efi":
bootloader = &EFIBootloader{}
case "linux":
bootloader = &LinuxBootloader{}
default:
return nil, fmt.Errorf("unknown bootloader type: %s", bootloaderType)
}
options := strvToOptions(optsStrv[1:])
if err := bootloader.FromOptions(options); err != nil {
return nil, err
}
return bootloader, nil
}

214
vendor/github.com/crc-org/vfkit/pkg/config/config.go generated vendored Normal file
View File

@@ -0,0 +1,214 @@
package config
import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
)
// VirtualMachine is the top-level type. It describes the virtual machine
// configuration (bootloader, devices, ...).
type VirtualMachine struct {
Vcpus uint `json:"vcpus"`
MemoryBytes uint64 `json:"memoryBytes"`
Bootloader Bootloader `json:"bootloader"`
Devices []VirtioDevice `json:"devices,omitempty"`
Timesync *TimeSync `json:"timesync,omitempty"`
}
// TimeSync enables synchronization of the host time to the linux guest after the host was suspended.
// This requires qemu-guest-agent to be running in the guest, and to be listening on a vsock socket
type TimeSync struct {
VsockPort uint
}
// The VMComponent interface represents a VM element (device, bootloader, ...)
// which can be converted from/to commandline parameters
type VMComponent interface {
FromOptions([]option) error
ToCmdLine() ([]string, error)
}
// NewVirtualMachine creates a new VirtualMachine instance. The virtual machine
// will use vcpus virtual CPUs and it will be allocated memoryBytes bytes of
// RAM. bootloader specifies which kernel/initrd/kernel args it will be using.
func NewVirtualMachine(vcpus uint, memoryBytes uint64, bootloader Bootloader) *VirtualMachine {
return &VirtualMachine{
Vcpus: vcpus,
MemoryBytes: memoryBytes,
Bootloader: bootloader,
}
}
// ToCmdLine generates a list of arguments for use with the [os/exec] package.
// These arguments will start a virtual machine with the devices/bootloader/...
// described by vm If the virtual machine configuration described by vm is
// invalid, an error will be returned.
func (vm *VirtualMachine) ToCmdLine() ([]string, error) {
// TODO: missing binary name/path
args := []string{}
if vm.Vcpus != 0 {
args = append(args, "--cpus", strconv.FormatUint(uint64(vm.Vcpus), 10))
}
if vm.MemoryBytes != 0 {
args = append(args, "--memory", strconv.FormatUint(vm.MemoryBytes, 10))
}
if vm.Bootloader == nil {
return nil, fmt.Errorf("missing bootloader configuration")
}
bootloaderArgs, err := vm.Bootloader.ToCmdLine()
if err != nil {
return nil, err
}
args = append(args, bootloaderArgs...)
for _, dev := range vm.Devices {
devArgs, err := dev.ToCmdLine()
if err != nil {
return nil, err
}
args = append(args, devArgs...)
}
return args, nil
}
func (vm *VirtualMachine) extraFiles() []*os.File {
extraFiles := []*os.File{}
for _, dev := range vm.Devices {
virtioNet, ok := dev.(*VirtioNet)
if !ok {
continue
}
if virtioNet.Socket != nil {
extraFiles = append(extraFiles, virtioNet.Socket)
}
}
return extraFiles
}
// Cmd creates an exec.Cmd to start vfkit with the configured devices.
// In particular it will set ExtraFiles appropriately when mapping
// a file with a network interface.
func (vm *VirtualMachine) Cmd(vfkitPath string) (*exec.Cmd, error) {
args, err := vm.ToCmdLine()
if err != nil {
return nil, err
}
cmd := exec.Command(vfkitPath, args...)
cmd.ExtraFiles = vm.extraFiles()
return cmd, nil
}
func (vm *VirtualMachine) AddDevicesFromCmdLine(cmdlineOpts []string) error {
for _, deviceOpts := range cmdlineOpts {
dev, err := deviceFromCmdLine(deviceOpts)
if err != nil {
return err
}
vm.Devices = append(vm.Devices, dev)
}
return nil
}
func (vm *VirtualMachine) VirtioGPUDevices() []*VirtioGPU {
gpuDevs := []*VirtioGPU{}
for _, dev := range vm.Devices {
if gpuDev, isVirtioGPU := dev.(*VirtioGPU); isVirtioGPU {
gpuDevs = append(gpuDevs, gpuDev)
}
}
return gpuDevs
}
func (vm *VirtualMachine) VirtioVsockDevices() []*VirtioVsock {
vsockDevs := []*VirtioVsock{}
for _, dev := range vm.Devices {
if vsockDev, isVirtioVsock := dev.(*VirtioVsock); isVirtioVsock {
vsockDevs = append(vsockDevs, vsockDev)
}
}
return vsockDevs
}
// AddDevice adds a dev to vm. This device can be created with one of the
// VirtioXXXNew methods.
func (vm *VirtualMachine) AddDevice(dev VirtioDevice) error {
vm.Devices = append(vm.Devices, dev)
return nil
}
func (vm *VirtualMachine) AddTimeSyncFromCmdLine(cmdlineOpts string) error {
if cmdlineOpts == "" {
return nil
}
timesync, err := timesyncFromCmdLine(cmdlineOpts)
if err != nil {
return err
}
vm.Timesync = timesync
return nil
}
func (vm *VirtualMachine) TimeSync() *TimeSync {
return vm.Timesync
}
func TimeSyncNew(vsockPort uint) (VMComponent, error) {
return &TimeSync{
VsockPort: vsockPort,
}, nil
}
func (ts *TimeSync) ToCmdLine() ([]string, error) {
args := []string{}
if ts.VsockPort != 0 {
args = append(args, fmt.Sprintf("vsockPort=%d", ts.VsockPort))
}
return []string{"--timesync", strings.Join(args, ",")}, nil
}
func (ts *TimeSync) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "vsockPort":
vsockPort, err := strconv.ParseUint(option.value, 10, 64)
if err != nil {
return err
}
ts.VsockPort = uint(vsockPort)
default:
return fmt.Errorf("Unknown option for timesync parameter: %s", option.key)
}
}
if ts.VsockPort == 0 {
return fmt.Errorf("Missing 'vsockPort' option for timesync parameter")
}
return nil
}
func timesyncFromCmdLine(optsStr string) (*TimeSync, error) {
var timesync TimeSync
optsStrv := strings.Split(optsStr, ",")
options := strvToOptions(optsStrv)
if err := timesync.FromOptions(options); err != nil {
return nil, err
}
return &timesync, nil
}

309
vendor/github.com/crc-org/vfkit/pkg/config/json.go generated vendored Normal file
View File

@@ -0,0 +1,309 @@
package config
import (
"encoding/json"
"fmt"
)
// The technique for json (de)serialization was explained here:
// http://gregtrowbridge.com/golang-json-serialization-with-interfaces/
type vmComponentKind string
const (
// Bootloader kinds
efiBootloader vmComponentKind = "efiBootloader"
linuxBootloader vmComponentKind = "linuxBootloader"
// VirtIO device kinds
vfNet vmComponentKind = "virtionet"
vfVsock vmComponentKind = "virtiosock"
vfBlk vmComponentKind = "virtioblk"
vfFs vmComponentKind = "virtiofs"
vfRng vmComponentKind = "virtiorng"
vfSerial vmComponentKind = "virtioserial"
vfGpu vmComponentKind = "virtiogpu"
vfInput vmComponentKind = "virtioinput"
usbMassStorage vmComponentKind = "usbmassstorage"
)
type jsonKind struct {
Kind vmComponentKind `json:"kind"`
}
func kind(k vmComponentKind) jsonKind {
return jsonKind{Kind: k}
}
func unmarshalBootloader(rawMsg json.RawMessage) (Bootloader, error) {
var (
kind jsonKind
bootloader Bootloader
err error
)
if err := json.Unmarshal(rawMsg, &kind); err != nil {
return nil, err
}
switch kind.Kind {
case efiBootloader:
var efi EFIBootloader
err = json.Unmarshal(rawMsg, &efi)
if err == nil {
bootloader = &efi
}
case linuxBootloader:
var linux LinuxBootloader
err = json.Unmarshal(rawMsg, &linux)
if err == nil {
bootloader = &linux
}
default:
err = fmt.Errorf("unknown 'kind' field: '%s'", kind)
}
return bootloader, err
}
func unmarshalDevices(rawMsg json.RawMessage) ([]VirtioDevice, error) {
var (
rawDevices []*json.RawMessage
devices []VirtioDevice
)
err := json.Unmarshal(rawMsg, &rawDevices)
if err != nil {
return nil, err
}
for _, msg := range rawDevices {
dev, err := unmarshalDevice(*msg)
if err != nil {
return nil, err
}
devices = append(devices, dev)
}
return devices, nil
}
func unmarshalDevice(rawMsg json.RawMessage) (VirtioDevice, error) {
var (
kind jsonKind
dev VirtioDevice
err error
)
if err := json.Unmarshal(rawMsg, &kind); err != nil {
return nil, err
}
switch kind.Kind {
case vfNet:
var newDevice VirtioNet
err = json.Unmarshal(rawMsg, &newDevice)
dev = &newDevice
case vfVsock:
var newDevice VirtioVsock
err = json.Unmarshal(rawMsg, &newDevice)
dev = &newDevice
case vfBlk:
var newDevice VirtioBlk
err = json.Unmarshal(rawMsg, &newDevice)
dev = &newDevice
case vfFs:
var newDevice VirtioFs
err = json.Unmarshal(rawMsg, &newDevice)
dev = &newDevice
case vfRng:
var newDevice VirtioRng
err = json.Unmarshal(rawMsg, &newDevice)
dev = &newDevice
case vfSerial:
var newDevice VirtioSerial
err = json.Unmarshal(rawMsg, &newDevice)
dev = &newDevice
case vfGpu:
var newDevice VirtioGPU
err = json.Unmarshal(rawMsg, &newDevice)
dev = &newDevice
case vfInput:
var newDevice VirtioInput
err = json.Unmarshal(rawMsg, &newDevice)
dev = &newDevice
case usbMassStorage:
var newDevice USBMassStorage
err = json.Unmarshal(rawMsg, &newDevice)
dev = &newDevice
default:
err = fmt.Errorf("unknown 'kind' field: '%s'", kind)
}
if err != nil {
return nil, err
}
return dev, nil
}
// UnmarshalJSON is a custom deserializer for VirtualMachine. The custom work
// is needed because VirtualMachine uses interfaces in its struct and JSON cannot
// determine which implementation of the interface to deserialize to.
func (vm *VirtualMachine) UnmarshalJSON(b []byte) error {
var (
err error
input map[string]*json.RawMessage
)
if err := json.Unmarshal(b, &input); err != nil {
return err
}
for idx, rawMsg := range input {
if rawMsg == nil {
continue
}
switch idx {
case "vcpus":
err = json.Unmarshal(*rawMsg, &vm.Vcpus)
case "memoryBytes":
err = json.Unmarshal(*rawMsg, &vm.MemoryBytes)
case "bootloader":
var bootloader Bootloader
bootloader, err = unmarshalBootloader(*rawMsg)
if err == nil {
vm.Bootloader = bootloader
}
case "timesync":
err = json.Unmarshal(*rawMsg, &vm.Timesync)
case "devices":
var devices []VirtioDevice
devices, err = unmarshalDevices(*rawMsg)
if err == nil {
vm.Devices = devices
}
}
if err != nil {
return err
}
}
return nil
}
func (bootloader *EFIBootloader) MarshalJSON() ([]byte, error) {
type blWithKind struct {
jsonKind
EFIBootloader
}
return json.Marshal(blWithKind{
jsonKind: kind(efiBootloader),
EFIBootloader: *bootloader,
})
}
func (bootloader *LinuxBootloader) MarshalJSON() ([]byte, error) {
type blWithKind struct {
jsonKind
LinuxBootloader
}
return json.Marshal(blWithKind{
jsonKind: kind(linuxBootloader),
LinuxBootloader: *bootloader,
})
}
func (dev *VirtioNet) MarshalJSON() ([]byte, error) {
type devWithKind struct {
jsonKind
VirtioNet
}
return json.Marshal(devWithKind{
jsonKind: kind(vfNet),
VirtioNet: *dev,
})
}
func (dev *VirtioVsock) MarshalJSON() ([]byte, error) {
type devWithKind struct {
jsonKind
VirtioVsock
}
return json.Marshal(devWithKind{
jsonKind: kind(vfVsock),
VirtioVsock: *dev,
})
}
func (dev *VirtioBlk) MarshalJSON() ([]byte, error) {
type devWithKind struct {
jsonKind
VirtioBlk
}
return json.Marshal(devWithKind{
jsonKind: kind(vfBlk),
VirtioBlk: *dev,
})
}
func (dev *VirtioFs) MarshalJSON() ([]byte, error) {
type devWithKind struct {
jsonKind
VirtioFs
}
return json.Marshal(devWithKind{
jsonKind: kind(vfFs),
VirtioFs: *dev,
})
}
func (dev *VirtioRng) MarshalJSON() ([]byte, error) {
type devWithKind struct {
jsonKind
VirtioRng
}
return json.Marshal(devWithKind{
jsonKind: kind(vfRng),
VirtioRng: *dev,
})
}
func (dev *VirtioSerial) MarshalJSON() ([]byte, error) {
type devWithKind struct {
jsonKind
VirtioSerial
}
return json.Marshal(devWithKind{
jsonKind: kind(vfSerial),
VirtioSerial: *dev,
})
}
func (dev *VirtioGPU) MarshalJSON() ([]byte, error) {
type devWithKind struct {
jsonKind
VirtioGPU
}
return json.Marshal(devWithKind{
jsonKind: kind(vfGpu),
VirtioGPU: *dev,
})
}
func (dev *VirtioInput) MarshalJSON() ([]byte, error) {
type devWithKind struct {
jsonKind
VirtioInput
}
return json.Marshal(devWithKind{
jsonKind: kind(vfInput),
VirtioInput: *dev,
})
}
func (dev *USBMassStorage) MarshalJSON() ([]byte, error) {
type devWithKind struct {
jsonKind
USBMassStorage
}
return json.Marshal(devWithKind{
jsonKind: kind(usbMassStorage),
USBMassStorage: *dev,
})
}

623
vendor/github.com/crc-org/vfkit/pkg/config/virtio.go generated vendored Normal file
View File

@@ -0,0 +1,623 @@
package config
import (
"fmt"
"net"
"os"
"strconv"
"strings"
)
// 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
VirtioGPUResolutionHeight = "height"
VirtioGPUResolutionWidth = "width"
// Default VirtioGPU Resolution
defaultVirtioGPUResolutionHeight = 800
defaultVirtioGPUResolutionWidth = 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 {
Height int `json:"height"`
Width int `json:"width"`
}
// 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 uint
// SocketURL is the path to a unix socket on the host to use for the virtio-vsock communication with the guest.
SocketURL string
// 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
}
// VirtioBlk configures a disk device.
type VirtioBlk struct {
StorageConfig
DeviceIdentifier string
}
// VirtioFs configures directory sharing between the guest and the host.
type VirtioFs struct {
SharedDir string
MountTag string
}
// 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
MacAddress net.HardwareAddr
// file parameter is holding a connected datagram socket.
// see https://github.com/Code-Hex/vz/blob/7f648b6fb9205d6f11792263d79876e3042c33ec/network.go#L113-L155
Socket *os.File
UnixSocketPath string
}
// VirtioSerial configures the virtual machine serial ports.
type VirtioSerial struct {
LogFile string
UsesStdio bool
}
// TODO: Add VirtioBalloon
// https://github.com/Code-Hex/vz/blob/master/memory_balloon.go
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 "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{}
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(fmt.Sprintf("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{
Height: defaultVirtioGPUResolutionHeight,
Width: defaultVirtioGPUResolutionWidth,
},
}, 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.Height, dev.Width)
}
return nil
}
func (dev *VirtioGPU) ToCmdLine() ([]string, error) {
if err := dev.validate(); err != nil {
return nil, err
}
return []string{"--device", fmt.Sprintf("virtio-gpu,height=%d,width=%d", dev.Height, dev.Width)}, 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(fmt.Sprintf("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(fmt.Sprintf("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
}
// 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 virtioBlkNewEmpty() *VirtioBlk {
return &VirtioBlk{
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.StorageConfig.FromOptions(unhandledOpts)
}
func (dev *VirtioBlk) ToCmdLine() ([]string, error) {
cmdLine, err := dev.StorageConfig.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) {
return &VirtioVsock{
Port: port,
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.Atoi(option.value)
if err != nil {
return err
}
dev.Port = uint(port)
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{
SharedDir: sharedDir,
MountTag: mountTag,
}, 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
}
type USBMassStorage struct {
StorageConfig
}
func usbMassStorageNewEmpty() *USBMassStorage {
return &USBMassStorage{
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) (VMComponent, error) {
usbMassStorage := usbMassStorageNewEmpty()
usbMassStorage.ImagePath = imagePath
return usbMassStorage, nil
}
// StorageConfig configures a disk device.
type StorageConfig struct {
DevName string
ImagePath string
ReadOnly bool
}
func (config *StorageConfig) ToCmdLine() ([]string, error) {
if config.ImagePath == "" {
return nil, fmt.Errorf("%s devices need the path to a disk image", config.DevName)
}
return []string{"--device", fmt.Sprintf("%s,path=%s", config.DevName, config.ImagePath)}, nil
}
func (config *StorageConfig) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "path":
config.ImagePath = option.value
default:
return fmt.Errorf("Unknown option for %s devices: %s", config.DevName, option.key)
}
}
return nil
}

10
vendor/github.com/crc-org/vfkit/pkg/rest/config.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
package rest
type ServiceScheme int
const (
TCP ServiceScheme = iota
Unix
None
HTTP
)

149
vendor/github.com/crc-org/vfkit/pkg/rest/rest.go generated vendored Normal file
View File

@@ -0,0 +1,149 @@
package rest
import (
"errors"
"fmt"
"net/url"
"strings"
"github.com/crc-org/vfkit/pkg/cmdline"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
type Endpoint struct {
Host string
Path string
Scheme ServiceScheme
}
func NewEndpoint(input string) (*Endpoint, error) {
uri, err := parseRestfulURI(input)
if err != nil {
return nil, err
}
scheme, err := toRestScheme(uri.Scheme)
if err != nil {
return nil, err
}
return &Endpoint{
Host: uri.Host,
Path: uri.Path,
Scheme: scheme,
}, nil
}
func (ep *Endpoint) ToCmdLine() ([]string, error) {
args := []string{"--restful-uri"}
switch ep.Scheme {
case Unix:
args = append(args, fmt.Sprintf("unix://%s", ep.Path))
case TCP:
args = append(args, fmt.Sprintf("tcp://%s%s", ep.Host, ep.Path))
case None:
return []string{}, nil
default:
return []string{}, errors.New("invalid endpoint scheme")
}
return args, nil
}
// VFKitService is used for the restful service; it describes
// the variables of the service like host/path but also has
// the router object
type VFKitService struct {
*Endpoint
router *gin.Engine
}
// Start initiates the already configured gin service
func (v *VFKitService) Start() {
go func() {
var err error
switch v.Scheme {
case TCP:
err = v.router.Run(v.Host)
case Unix:
err = v.router.RunUnix(v.Path)
}
logrus.Fatal(err)
}()
}
// NewServer creates a new restful service
func NewServer(inspector VirtualMachineInspector, stateHandler VirtualMachineStateHandler, endpoint string) (*VFKitService, error) {
r := gin.Default()
ep, err := NewEndpoint(endpoint)
if err != nil {
return nil, err
}
s := VFKitService{
router: r,
Endpoint: ep,
}
// Handlers for the restful service. This is where endpoints are defined.
r.GET("/vm/state", stateHandler.GetVMState)
r.POST("/vm/state", stateHandler.SetVMState)
r.GET("/vm/inspect", inspector.Inspect)
return &s, nil
}
type VirtualMachineInspector interface {
Inspect(c *gin.Context)
}
type VirtualMachineStateHandler interface {
GetVMState(c *gin.Context)
SetVMState(c *gin.Context)
}
// parseRestfulURI validates the input URI and returns an URL object
func parseRestfulURI(inputURI string) (*url.URL, error) {
restURI, err := url.ParseRequestURI(inputURI)
if err != nil {
return nil, err
}
scheme, err := toRestScheme(restURI.Scheme)
if err != nil {
return nil, err
}
if scheme == TCP && len(restURI.Host) < 1 {
return nil, errors.New("invalid TCP uri: missing host")
}
if scheme == TCP && len(restURI.Path) > 0 {
return nil, errors.New("invalid TCP uri: path is forbidden")
}
if scheme == TCP && restURI.Port() == "" {
return nil, errors.New("invalid TCP uri: missing port")
}
if scheme == Unix && len(restURI.Path) < 1 {
return nil, errors.New("invalid unix uri: missing path")
}
if scheme == Unix && len(restURI.Host) > 0 {
return nil, errors.New("invalid unix uri: host is forbidden")
}
return restURI, err
}
// toRestScheme converts a string to a ServiceScheme
func toRestScheme(s string) (ServiceScheme, error) {
switch strings.ToUpper(s) {
case "NONE":
return None, nil
case "UNIX":
return Unix, nil
case "TCP", "HTTP":
return TCP, nil
}
return None, fmt.Errorf("invalid scheme %s", s)
}
func validateRestfulURI(inputURI string) error {
if inputURI != cmdline.DefaultRestfulURI {
if _, err := parseRestfulURI(inputURI); err != nil {
return err
}
}
return nil
}

23
vendor/github.com/crc-org/vfkit/pkg/util/strings.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package util
import "strings"
func TrimQuotes(str string) string {
if strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) {
str = strings.Trim(str, `"`)
}
return str
}
func StringInSlice(st string, sl []string) bool {
if sl == nil {
return false
}
for _, s := range sl {
if st == s {
return true
}
}
return false
}