mirror of
https://github.com/containers/podman.git
synced 2025-11-30 10:07:33 +08:00
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:
53
vendor/github.com/crc-org/vfkit/pkg/cmdline/cmdline.go
generated
vendored
Normal file
53
vendor/github.com/crc-org/vfkit/pkg/cmdline/cmdline.go
generated
vendored
Normal 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")
|
||||
|
||||
}
|
||||
128
vendor/github.com/crc-org/vfkit/pkg/cmdline/string_slice.go
generated
vendored
Normal file
128
vendor/github.com/crc-org/vfkit/pkg/cmdline/string_slice.go
generated
vendored
Normal 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
|
||||
}
|
||||
143
vendor/github.com/crc-org/vfkit/pkg/config/bootloader.go
generated
vendored
Normal file
143
vendor/github.com/crc-org/vfkit/pkg/config/bootloader.go
generated
vendored
Normal 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
214
vendor/github.com/crc-org/vfkit/pkg/config/config.go
generated
vendored
Normal 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 ×ync, nil
|
||||
}
|
||||
309
vendor/github.com/crc-org/vfkit/pkg/config/json.go
generated
vendored
Normal file
309
vendor/github.com/crc-org/vfkit/pkg/config/json.go
generated
vendored
Normal 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
623
vendor/github.com/crc-org/vfkit/pkg/config/virtio.go
generated
vendored
Normal 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
10
vendor/github.com/crc-org/vfkit/pkg/rest/config.go
generated
vendored
Normal 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
149
vendor/github.com/crc-org/vfkit/pkg/rest/rest.go
generated
vendored
Normal 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
23
vendor/github.com/crc-org/vfkit/pkg/util/strings.go
generated
vendored
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user