Files
kata-containers/qemu.go
Samuel Ortiz e543c3383d qemu: Probe each qemu device with a driver
Having separate structures for the qemu driver definitions
and each possible device definitions is confusing and error prone as one
needs to be very careful using matching IDs and names in both
structures.

As the driver parameter can be derived from the device
ones, this patch changes the Device and Driver structures to be linked
together, i.e. each driver needs to have its corresponding device.

For example this allows us to build the following 9pfs qemu parameters:

"-fsdev local,id=foo,path=/bar/foo,security-model=none -device virtio-9p-pci,fsdev=foo,mount_tag=rootfs"

from these structures:

	fsdev := FSDevice{
		Driver:        Local,
		ID:            "foo",
		Path:          "/bar/foo",
		MountTag:      "rootfs",
		SecurityModel: None,
	}

	driver := Driver{
		Driver: Virtio9P,
		Device: fsdev,
	}

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2016-09-17 00:41:13 +02:00

871 lines
21 KiB
Go

/*
// Copyright (c) 2016 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
// Package qemu provides methods and types for launching and managing QEMU
// instances. Instances can be launched with the LaunchQemu function and
// managed thereafter via QMPStart and the QMP object that this function
// returns. To manage a qemu instance after it has been launched you need
// to pass the -qmp option during launch requesting the qemu instance to create
// a QMP unix domain manageent socket, e.g.,
// -qmp unix:/tmp/qmp-socket,server,nowait. For more information see the
// example below.
package qemu
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"context"
)
// Machine describes the machine type qemu will emulate.
type Machine struct {
// Type is the machine type to be used by qemu.
Type string
// Acceleration are the machine acceleration options to be used by qemu.
Acceleration string
}
// Device is the qemu device interface.
type Device interface {
Valid() bool
}
// DeviceDriver is the Device driver string.
type DeviceDriver string
const (
// NVDIMM is the Non Volatile DIMM device driver.
NVDIMM DeviceDriver = "nvdimm"
// Virtio9P is the 9pfs device driver.
Virtio9P = "virtio-9p-pci"
// VirtioNet is the virt-io networking device driver.
VirtioNet = "virtio-net"
// VirtioSerial is the serial device driver.
VirtioSerial = "virtio-serial-pci"
// Console is the console device driver.
Console = "virtconsole"
)
// Driver describes a driver to be used by qemu.
type Driver struct {
// Driver is the qemu device driver
Driver DeviceDriver
// ID is the user defined device ID.
ID string
// Device is the device to be associated with this driver.
Device Device
}
func (driver Driver) deviceMatch() bool {
switch driver.Device.(type) {
case Object:
if driver.Driver != NVDIMM {
return false
}
case FSDevice:
if driver.Driver != Virtio9P {
return false
}
case SerialDevice:
if driver.Driver != VirtioSerial {
return false
}
case CharDevice:
if driver.Driver != Console {
return false
}
case NetDevice:
if driver.Driver != VirtioNet {
return false
}
default:
return false
}
return true
}
// Valid returns true if the Driver structure is valid and complete.
func (driver Driver) Valid() bool {
switch driver.Driver {
case NVDIMM:
case Console:
case VirtioSerial:
if driver.ID == "" || driver.Device.Valid() == false {
return false
}
case Virtio9P:
case VirtioNet:
return driver.Device.Valid()
default:
return false
}
// Verify that the Device type matches the Driver.
return driver.deviceMatch()
}
// ObjectType is a string representing a qemu object type.
type ObjectType string
const (
// MemoryBackendFile represents a guest memory mapped file.
MemoryBackendFile ObjectType = "memory-backend-file"
)
// Object is a qemu object representation.
type Object struct {
// Type is the qemu object type.
Type ObjectType
// ID is the user defined object ID.
ID string
// MemPath is the object's memory path.
// This is only relevant for memory objects
MemPath string
// Size is the object size in bytes
Size uint64
}
// Valid returns true if the Object structure is valid and complete.
func (object Object) Valid() bool {
switch object.Type {
case MemoryBackendFile:
if object.ID == "" || object.MemPath == "" || object.Size == 0 {
return false
}
}
return true
}
// FSDriver represents a qemu filesystem driver.
type FSDriver string
// SecurityModelType is a qemu filesystem security model type.
type SecurityModelType string
const (
// Local is the local qemu filesystem driver.
Local FSDriver = "local"
// Handle is the handle qemu filesystem driver.
Handle = "handle"
// Proxy is the proxy qemu filesystem driver.
Proxy = "proxy"
)
const (
// None is like passthrough without failure reports.
None SecurityModelType = "none"
// PassThrough uses the same credentials on both the host and guest.
PassThrough = "passthrough"
// MappedXattr stores some files attributes as extended attributes.
MappedXattr = "mapped-xattr"
// MappedFile stores some files attributes in the .virtfs directory.
MappedFile = "mapped-file"
)
// FSDevice represents a qemu filesystem configuration.
type FSDevice struct {
// Driver is the filesystem driver backend.
Driver FSDriver
// ID is the filesystem identifier.
ID string
// Path is the host root path for this filesystem.
Path string
// MountTag is the device filesystem mount point tag.
MountTag string
// SecurityModel is the security model for this filesystem device.
SecurityModel SecurityModelType
}
// Valid returns true if the FSDevice structure is valid and complete.
func (fsdev FSDevice) Valid() bool {
if fsdev.ID == "" || fsdev.Path == "" || fsdev.MountTag == "" {
return false
}
return true
}
// CharDeviceBackend is the character device backend for qemu
type CharDeviceBackend string
const (
// Pipe creates a 2 way connection to the guest.
Pipe CharDeviceBackend = "pipe"
// Socket creates a 2 way stream socket (TCP or Unix).
Socket = "socket"
// CharConsole sends traffic from the guest to QEMU's standard output.
CharConsole = "console"
// Serial sends traffic from the guest to a serial device on the host.
Serial = "serial"
// TTY is an alias for Serial.
TTY = "tty"
// PTY creates a new pseudo-terminal on the host and connect to it.
PTY = "pty"
)
// CharDevice represents a qemu character device.
type CharDevice struct {
Backend CharDeviceBackend
ID string
Path string
}
// Valid returns true if the CharDevice structure is valid and complete.
func (cdev CharDevice) Valid() bool {
if cdev.ID == "" || cdev.Path == "" {
return false
}
return true
}
// NetDeviceType is a qemu networing device type.
type NetDeviceType string
const (
// TAP is a TAP networking device type.
TAP NetDeviceType = "tap"
)
// NetDevice represents a guest networking device
type NetDevice struct {
// Type is the netdev type (e.g. tap).
Type NetDeviceType
// ID is the netdevice identifier.
ID string
// IfName is the interface name,
IFName string
// DownScript is the tap interface deconfiguration script.
DownScript string
// Script is the tap interface configuration script.
Script string
// FDs represents the list of already existing file descriptors to be used.
// This is mostly useful for mq support.
FDs []int
// VHost enables virtio device emulation from the host kernel instead of from qemu.
VHost bool
// MACAddress is the networking device interface MAC address.
MACAddress string
}
// Valid returns true if the NetDevice structure is valid and complete.
func (netdev NetDevice) Valid() bool {
if netdev.ID == "" || netdev.IFName == "" {
return false
}
switch netdev.Type {
case TAP:
return true
default:
return false
}
}
// SerialDevice represents a qemu serial device.
type SerialDevice struct {
}
// Valid returns true if the SerialDevice structure is valid and complete.
func (dev SerialDevice) Valid() bool {
return true
}
// RTC represents a qemu Real Time Clock configuration.
type RTC struct {
// Base is the RTC start time.
Base string
// Clock is the is the RTC clock driver.
Clock string
// DriftFix is the drift fixing mechanism.
DriftFix string
}
// QMPSocket represents a qemu QMP socket configuration.
type QMPSocket struct {
// Type is the socket type (e.g. "unix").
Type string
// Name is the socket name.
Name string
// Server tells if this is a server socket.
Server bool
// NoWait tells if qemu should block waiting for a client to connect.
NoWait bool
}
// SMP is the multi processors configuration structure.
type SMP struct {
// CPUs is the number of VCPUs made available to qemu.
CPUs uint32
// Cores is the number of cores made available to qemu.
Cores uint32
// Threads is the number of threads made available to qemu.
Threads uint32
// Sockets is the number of sockets made available to qemu.
Sockets uint32
}
// Memory is the guest memory configuration structure.
type Memory struct {
// Size is the amount of memory made available to the guest.
// It should be suffixed with M or G for sizes in megabytes or
// gigabytes respectively.
Size string
// Slots is the amount of memory slots made available to the guest.
Slots uint8
// MaxMem is the maximum amount of memory that can be made available
// to the guest through e.g. hot pluggable memory.
MaxMem string
}
// Kernel is the guest kernel configuration structure.
type Kernel struct {
// Path is the guest kernel path on the host filesystem.
Path string
// Params is the kernel parameters string.
Params string
}
// Knobs regroups a set of qemu boolean settings
type Knobs struct {
// NoUserConfig prevents qemu from loading user config files.
NoUserConfig bool
// NoDefaults prevents qemu from creating default devices.
NoDefaults bool
// NoGraphic completely disables graphic output.
NoGraphic bool
}
// Config is the qemu configuration structure.
// It allows for passing custom settings and parameters to the qemu API.
type Config struct {
// Path is the qemu binary path.
Path string
// Ctx is not used at the moment.
Ctx context.Context
// Name is the qemu guest name
Name string
// UUID is the qemu process UUID.
UUID string
// CPUModel is the CPU model to be used by qemu.
CPUModel string
// Machine
Machine Machine
// QMPSocket is the QMP socket description.
QMPSocket QMPSocket
// Drivers is a list of device drivers for qemu to use.
Drivers []Driver
// RTC is the qemu Real Time Clock configuration
RTC RTC
// VGA is the qemu VGA mode.
VGA string
// Kernel is the guest kernel configuration.
Kernel Kernel
// Memory is the guest memory configuration.
Memory Memory
// SMP is the quest multi processors configuration.
SMP SMP
// GlobalParam is the -global parameter.
GlobalParam string
// Knobs is a set of qemu boolean settings.
Knobs Knobs
// FDs is a list of open file descriptors to be passed to the spawned qemu process
FDs []*os.File
}
func appendName(params []string, config Config) []string {
if config.Name != "" {
params = append(params, "-name")
params = append(params, config.Name)
}
return params
}
func appendMachine(params []string, config Config) []string {
if config.Machine.Type != "" {
var machineParams []string
machineParams = append(machineParams, config.Machine.Type)
if config.Machine.Acceleration != "" {
machineParams = append(machineParams, fmt.Sprintf(",accel=%s", config.Machine.Acceleration))
}
params = append(params, "-machine")
params = append(params, strings.Join(machineParams, ""))
}
return params
}
func appendCPUModel(params []string, config Config) []string {
if config.CPUModel != "" {
params = append(params, "-cpu")
params = append(params, config.CPUModel)
}
return params
}
func appendQMPSocket(params []string, config Config) []string {
if config.QMPSocket.Type != "" && config.QMPSocket.Name != "" {
var qmpParams []string
qmpParams = append(qmpParams, fmt.Sprintf("%s:", config.QMPSocket.Type))
qmpParams = append(qmpParams, fmt.Sprintf("%s", config.QMPSocket.Name))
if config.QMPSocket.Server == true {
qmpParams = append(qmpParams, ",server")
if config.QMPSocket.NoWait == true {
qmpParams = append(qmpParams, ",nowait")
}
}
params = append(params, "-qmp")
params = append(params, strings.Join(qmpParams, ""))
}
return params
}
func appendObject(params []string, object Object) ([]string, error) {
if object.Valid() == false {
return nil, fmt.Errorf("Invalid Object")
}
var objectParams []string
objectParams = append(objectParams, string(object.Type))
switch object.Type {
case MemoryBackendFile:
objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID))
objectParams = append(objectParams, fmt.Sprintf(",mem-path=%s", object.MemPath))
objectParams = append(objectParams, fmt.Sprintf(",size=%d", object.Size))
}
params = append(params, "-object")
params = append(params, strings.Join(objectParams, ""))
return params, nil
}
func appendFilesystemDevice(params []string, fsdev FSDevice) ([]string, error) {
if fsdev.Valid() == false {
return nil, fmt.Errorf("Invalid filesystem device")
}
var fsParams []string
fsParams = append(fsParams, string(fsdev.Driver))
fsParams = append(fsParams, fmt.Sprintf(",id=%s", fsdev.ID))
fsParams = append(fsParams, fmt.Sprintf(",path=%s", fsdev.Path))
fsParams = append(fsParams, fmt.Sprintf(",security-model=%s", fsdev.SecurityModel))
params = append(params, "-fsdev")
params = append(params, strings.Join(fsParams, ""))
return params, nil
}
func appendCharDevice(params []string, cdev CharDevice) ([]string, error) {
if cdev.Valid() == false {
return nil, fmt.Errorf("Invalid character device")
}
var cdevParams []string
cdevParams = append(cdevParams, string(cdev.Backend))
cdevParams = append(cdevParams, fmt.Sprintf(",id=%s", cdev.ID))
cdevParams = append(cdevParams, fmt.Sprintf(",path=%s", cdev.Path))
params = append(params, "-chardev")
params = append(params, strings.Join(cdevParams, ""))
return params, nil
}
func appendNetDevice(params []string, netdev NetDevice) ([]string, error) {
if netdev.Valid() == false {
return nil, fmt.Errorf("Invalid network device")
}
var netdevParams []string
netdevParams = append(netdevParams, string(netdev.Type))
netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", netdev.ID))
netdevParams = append(netdevParams, fmt.Sprintf(",ifname=%s", netdev.IFName))
if netdev.DownScript != "" {
netdevParams = append(netdevParams, fmt.Sprintf(",downscript=%s", netdev.DownScript))
}
if netdev.Script != "" {
netdevParams = append(netdevParams, fmt.Sprintf(",script=%s", netdev.Script))
}
if len(netdev.FDs) > 0 {
var fdParams []string
for _, fd := range netdev.FDs {
fdParams = append(fdParams, fmt.Sprintf("%d", fd))
}
netdevParams = append(netdevParams, fmt.Sprintf(",fds=%s", strings.Join(fdParams, ":")))
}
if netdev.VHost == true {
netdevParams = append(netdevParams, ",vhost=on")
}
params = append(params, "-netdev")
params = append(params, strings.Join(netdevParams, ""))
return params, nil
}
func appendDrivers(params []string, config Config) []string {
for _, d := range config.Drivers {
if d.Valid() == false {
continue
}
var driverParams []string
var err error
driverParams = append(driverParams, fmt.Sprintf("%s", d.Driver))
switch device := d.Device.(type) {
case Object:
params, err = appendObject(params, device)
if err != nil {
continue
}
driverParams = append(driverParams, fmt.Sprintf(",id=%s", d.ID))
driverParams = append(driverParams, fmt.Sprintf(",memdev=%s", device.ID))
case FSDevice:
params, err = appendFilesystemDevice(params, device)
if err != nil {
continue
}
driverParams = append(driverParams, fmt.Sprintf(",fsdev=%s", device.ID))
driverParams = append(driverParams, fmt.Sprintf(",mount_tag=%s", device.MountTag))
case SerialDevice:
driverParams = append(driverParams, fmt.Sprintf(",id=%s", d.ID))
case CharDevice:
params, err = appendCharDevice(params, device)
if err != nil {
continue
}
driverParams = append(driverParams, fmt.Sprintf(",chardev=%s", device.ID))
driverParams = append(driverParams, fmt.Sprintf(",id=%s", d.ID))
case NetDevice:
params, err = appendNetDevice(params, device)
if err != nil {
continue
}
driverParams = append(driverParams, fmt.Sprintf(",netdev=%s", device.ID))
driverParams = append(driverParams, fmt.Sprintf(",mac=%s", device.MACAddress))
}
params = append(params, "-device")
params = append(params, strings.Join(driverParams, ""))
}
return params
}
func appendUUID(params []string, config Config) []string {
if config.UUID != "" {
params = append(params, "-uuid")
params = append(params, config.UUID)
}
return params
}
func appendMemory(params []string, config Config) []string {
if config.Memory.Size != "" {
var memoryParams []string
memoryParams = append(memoryParams, config.Memory.Size)
if config.Memory.Slots > 0 {
memoryParams = append(memoryParams, fmt.Sprintf(",slots=%d", config.Memory.Slots))
}
if config.Memory.MaxMem != "" {
memoryParams = append(memoryParams, fmt.Sprintf(",maxmem=%s", config.Memory.MaxMem))
}
params = append(params, "-m")
params = append(params, strings.Join(memoryParams, ""))
}
return params
}
func appendCPUs(params []string, config Config) []string {
if config.SMP.CPUs > 0 {
var SMPParams []string
SMPParams = append(SMPParams, fmt.Sprintf("%d", config.SMP.CPUs))
if config.SMP.Cores > 0 {
SMPParams = append(SMPParams, fmt.Sprintf(",cores=%d", config.SMP.Cores))
}
if config.SMP.Threads > 0 {
SMPParams = append(SMPParams, fmt.Sprintf(",threads=%d", config.SMP.Threads))
}
if config.SMP.Sockets > 0 {
SMPParams = append(SMPParams, fmt.Sprintf(",sockets=%d", config.SMP.Sockets))
}
params = append(params, "-smp")
params = append(params, strings.Join(SMPParams, ""))
}
return params
}
func appendRTC(params []string, config Config) []string {
if config.RTC.Base != "" {
var RTCParams []string
RTCParams = append(RTCParams, fmt.Sprintf("base=%s", config.RTC.Base))
if config.RTC.DriftFix != "" {
RTCParams = append(RTCParams, fmt.Sprintf(",driftfix=%s", config.RTC.DriftFix))
}
if config.RTC.Clock != "" {
RTCParams = append(RTCParams, fmt.Sprintf(",clock=%s", config.RTC.Clock))
}
params = append(params, "-rtc")
params = append(params, strings.Join(RTCParams, ""))
}
return params
}
func appendGlobalParam(params []string, config Config) []string {
if config.GlobalParam != "" {
params = append(params, "-global")
params = append(params, config.GlobalParam)
}
return params
}
func appendVGA(params []string, config Config) []string {
if config.VGA != "" {
params = append(params, "-vga")
params = append(params, config.VGA)
}
return params
}
func appendKernel(params []string, config Config) []string {
if config.Kernel.Path != "" {
params = append(params, "-kernel")
params = append(params, config.Kernel.Path)
if config.Kernel.Params != "" {
params = append(params, "-append")
params = append(params, config.Kernel.Params)
}
}
return params
}
func appendKnobs(params []string, config Config) []string {
if config.Knobs.NoUserConfig == true {
params = append(params, "-no-user-config")
}
if config.Knobs.NoDefaults == true {
params = append(params, "-nodefaults")
}
if config.Knobs.NoGraphic == true {
params = append(params, "-nographic")
}
return params
}
// LaunchQemu can be used to launch a new qemu instance.
//
// The Config parameter contains a set of qemu parameters and settings.
//
// This function writes its log output via logger parameter.
//
// The function will block until the launched qemu process exits. "", nil
// will be returned if the launch succeeds. Otherwise a string containing
// the contents of stderr + a Go error object will be returned.
func LaunchQemu(config Config, logger QMPLog) (string, error) {
var params []string
params = appendName(params, config)
params = appendUUID(params, config)
params = appendMachine(params, config)
params = appendCPUModel(params, config)
params = appendQMPSocket(params, config)
params = appendMemory(params, config)
params = appendCPUs(params, config)
params = appendDrivers(params, config)
params = appendRTC(params, config)
params = appendGlobalParam(params, config)
params = appendVGA(params, config)
params = appendKnobs(params, config)
params = appendKernel(params, config)
return LaunchCustomQemu(config.Ctx, config.Path, params, config.FDs, logger)
}
// LaunchCustomQemu can be used to launch a new qemu instance.
//
// The path parameter is used to pass the qemu executable path.
//
// The ctx parameter is not currently used but has been added so that the
// signature of this function will not need to change when launch cancellation
// is implemented.
//
// params is a slice of options to pass to qemu-system-x86_64 and fds is a
// list of open file descriptors that are to be passed to the spawned qemu
// process.
//
// This function writes its log output via logger parameter.
//
// The function will block until the launched qemu process exits. "", nil
// will be returned if the launch succeeds. Otherwise a string containing
// the contents of stderr + a Go error object will be returned.
func LaunchCustomQemu(ctx context.Context, path string, params []string, fds []*os.File, logger QMPLog) (string, error) {
if logger == nil {
logger = qmpNullLogger{}
}
errStr := ""
if path == "" {
path = "qemu-system-x86_64"
}
cmd := exec.Command(path, params...)
if len(fds) > 0 {
logger.Infof("Adding extra file %v", fds)
cmd.ExtraFiles = fds
}
var stderr bytes.Buffer
cmd.Stderr = &stderr
logger.Infof("launching qemu with: %v", params)
err := cmd.Run()
if err != nil {
logger.Errorf("Unable to launch qemu: %v", err)
errStr = stderr.String()
logger.Errorf("%s", errStr)
}
return errStr, err
}