diff --git a/ciao-launcher/qemu.go b/ciao-launcher/qemu.go index 0480ad4c2..15f0c1486 100644 --- a/ciao-launcher/qemu.go +++ b/ciao-launcher/qemu.go @@ -361,7 +361,8 @@ func launchQemuWithNC(params []string, fds []*os.File, ipAddress string) (int, e ncString := "socket,port=%d,host=%s,server,id=gnc0,server,nowait" params[len(params)-1] = fmt.Sprintf(ncString, port, ipAddress) var errStr string - errStr, err = qemu.LaunchQemu(context.Background(), params, fds, qmpGlogLogger{}) + + errStr, err = qemu.LaunchCustomQemu(context.Background(), "", params, fds, qmpGlogLogger{}) if err == nil { glog.Info("============================================") glog.Infof("Connect to vm with netcat %s %d", ipAddress, port) @@ -378,7 +379,7 @@ func launchQemuWithNC(params []string, fds []*os.File, ipAddress string) (int, e if port == 0 || (err != nil && tries == vcTries) { glog.Warning("Failed to launch qemu due to chardev error. Relaunching without virtual console") - _, err = qemu.LaunchQemu(context.Background(), params[:len(params)-4], fds, qmpGlogLogger{}) + _, err = qemu.LaunchCustomQemu(context.Background(), "", params[:len(params)-4], fds, qmpGlogLogger{}) } return port, err @@ -397,7 +398,7 @@ func launchQemuWithSpice(params []string, fds []*os.File, ipAddress string) (int } params[len(params)-1] = fmt.Sprintf("port=%d,addr=%s,disable-ticketing", port, ipAddress) var errStr string - errStr, err = qemu.LaunchQemu(context.Background(), params, fds, qmpGlogLogger{}) + errStr, err = qemu.LaunchCustomQemu(context.Background(), "", params, fds, qmpGlogLogger{}) if err == nil { glog.Info("============================================") glog.Infof("Connect to vm with spicec -h %s -p %d", ipAddress, port) @@ -416,7 +417,7 @@ func launchQemuWithSpice(params []string, fds []*os.File, ipAddress string) (int if port == 0 || (err != nil && tries == vcTries) { glog.Warning("Failed to launch qemu due to spice error. Relaunching without virtual console") params = append(params[:len(params)-2], "-display", "none", "-vga", "none") - _, err = qemu.LaunchQemu(context.Background(), params, fds, qmpGlogLogger{}) + _, err = qemu.LaunchCustomQemu(context.Background(), "", params, fds, qmpGlogLogger{}) } return port, err @@ -519,7 +520,7 @@ func (q *qemuV) startVM(vnicName, ipAddress, cephID string) error { if !launchWithUI.Enabled() { params = append(params, "-display", "none", "-vga", "none") - _, err = qemu.LaunchQemu(context.Background(), params, fds, qmpGlogLogger{}) + _, err = qemu.LaunchCustomQemu(context.Background(), "", params, fds, qmpGlogLogger{}) } else if launchWithUI.String() == "spice" { var port int port, err = launchQemuWithSpice(params, fds, ipAddress) diff --git a/qemu/examples_test.go b/qemu/examples_test.go index e7ed12abb..45607a044 100644 --- a/qemu/examples_test.go +++ b/qemu/examples_test.go @@ -38,10 +38,10 @@ func Example() { // resources params = append(params, "-m", "370", "-smp", "cpus=2") - // LaunchQemu should return as soon as the instance has launched as we + // LaunchCustomQemu should return as soon as the instance has launched as we // are using the --daemonize flag. It will set up a unix domain socket // called /tmp/qmp-socket that we can use to manage the instance. - _, err := qemu.LaunchQemu(context.Background(), params, nil, nil) + _, err := qemu.LaunchCustomQemu(context.Background(), "", params, nil, nil) if err != nil { panic(err) } diff --git a/qemu/qemu.go b/qemu/qemu.go index 4fe3b2b93..869ae3be5 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -26,20 +26,943 @@ package qemu import ( "bytes" + "fmt" "os" "os/exec" + "strings" "context" ) -// LaunchQemu can be used to launch a new qemu instance by invoking the -// qemu-system-x86_64 binary. +// 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 + QemuParams(config *Config) []string +} + +// 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" + + // VirtioBlock is the block device driver. + VirtioBlock = "virtio-blk" + + // Console is the console device driver. + Console = "virtconsole" +) + +// 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 { + // Driver is the qemu device driver + Driver DeviceDriver + + // Type is the qemu object type. + Type ObjectType + + // ID is the user defined object ID. + ID string + + // DeviceID is the user defined device ID. + DeviceID 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 + } + + default: + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of this Object device. +func (object Object) QemuParams(config *Config) []string { + var objectParams []string + var deviceParams []string + var qemuParams []string + + deviceParams = append(deviceParams, string(object.Driver)) + deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", object.DeviceID)) + + switch object.Type { + case MemoryBackendFile: + objectParams = append(objectParams, string(object.Type)) + 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)) + + deviceParams = append(deviceParams, fmt.Sprintf(",memdev=%s", object.ID)) + } + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + + qemuParams = append(qemuParams, "-object") + qemuParams = append(qemuParams, strings.Join(objectParams, "")) + + return qemuParams +} + +// 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 qemu device driver + Driver DeviceDriver + + // FSDriver is the filesystem driver backend. + FSDriver 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 +} + +// QemuParams returns the qemu parameters built out of this filesystem device. +func (fsdev FSDevice) QemuParams(config *Config) []string { + var fsParams []string + var deviceParams []string + var qemuParams []string + + deviceParams = append(deviceParams, fmt.Sprintf("%s", fsdev.Driver)) + deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", fsdev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", fsdev.MountTag)) + + fsParams = append(fsParams, string(fsdev.FSDriver)) + 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)) + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + + qemuParams = append(qemuParams, "-fsdev") + qemuParams = append(qemuParams, strings.Join(fsParams, "")) + + return qemuParams +} + +// 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 + + // Driver is the qemu device driver + Driver DeviceDriver + + // DeviceID is the user defined device ID. + DeviceID string + + 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 +} + +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 +} + +// QemuParams returns the qemu parameters built out of this character device. +func (cdev CharDevice) QemuParams(config *Config) []string { + var cdevParams []string + var deviceParams []string + var qemuParams []string + + deviceParams = append(deviceParams, fmt.Sprintf("%s", cdev.Driver)) + deviceParams = append(deviceParams, fmt.Sprintf(",chardev=%s", cdev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", cdev.DeviceID)) + + cdevParams = append(cdevParams, string(cdev.Backend)) + cdevParams = append(cdevParams, fmt.Sprintf(",id=%s", cdev.ID)) + cdevParams = append(cdevParams, fmt.Sprintf(",path=%s", cdev.Path)) + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + + qemuParams = append(qemuParams, "-chardev") + qemuParams = append(qemuParams, strings.Join(cdevParams, "")) + + return qemuParams +} + +// NetDeviceType is a qemu networing device type. +type NetDeviceType string + +const ( + // TAP is a TAP networking device type. + TAP NetDeviceType = "tap" + + // MACVTAP is a MAC virtual TAP networking device type. + MACVTAP = "macvtap" +) + +// NetDevice represents a guest networking device +type NetDevice struct { + // Type is the netdev type (e.g. tap). + Type NetDeviceType + + // Driver is the qemu device driver + Driver DeviceDriver + + // 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 []*os.File + + // 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 + case MACVTAP: + return true + default: + return false + } +} + +// QemuParams returns the qemu parameters built out of this network device. +func (netdev NetDevice) QemuParams(config *Config) []string { + var netdevParams []string + var deviceParams []string + var qemuParams []string + + deviceParams = append(deviceParams, fmt.Sprintf("%s", netdev.Driver)) + deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", netdev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", netdev.MACAddress)) + + 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 + + qemuFDs := config.appendFDs(netdev.FDs) + + for _, fd := range qemuFDs { + 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") + } + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + + qemuParams = append(qemuParams, "-netdev") + qemuParams = append(qemuParams, strings.Join(netdevParams, "")) + + return qemuParams +} + +// SerialDevice represents a qemu serial device. +type SerialDevice struct { + // Driver is the qemu device driver + Driver DeviceDriver + + // ID is the serial device identifier. + ID string +} + +// Valid returns true if the SerialDevice structure is valid and complete. +func (dev SerialDevice) Valid() bool { + if dev.Driver == "" || dev.ID == "" { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of this serial device. +func (dev SerialDevice) QemuParams(config *Config) []string { + var deviceParams []string + var qemuParams []string + + deviceParams = append(deviceParams, fmt.Sprintf("%s", dev.Driver)) + deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", dev.ID)) + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + + return qemuParams +} + +// BlockDeviceInterface defines the type of interface the device is connected to. +type BlockDeviceInterface string + +// BlockDeviceAIO defines the type of asynchronous I/O the block device should use. +type BlockDeviceAIO string + +// BlockDeviceFormat defines the image format used on a block device. +type BlockDeviceFormat string + +const ( + // NoInterface for block devices with no interfaces. + NoInterface BlockDeviceInterface = "none" + + // SCSI represents a SCSI block device interface. + SCSI = "scsi" +) + +const ( + // Threads is the pthread asynchronous I/O implementation. + Threads BlockDeviceAIO = "threads" + + // Native is the pthread asynchronous I/O implementation. + Native = "native" +) + +const ( + // QCOW2 is the Qemu Copy On Write v2 image format. + QCOW2 BlockDeviceFormat = "qcow2" +) + +// BlockDevice represents a qemu block device. +type BlockDevice struct { + Driver DeviceDriver + ID string + File string + Interface BlockDeviceInterface + AIO BlockDeviceAIO + Format BlockDeviceFormat + SCSI bool + WCE bool +} + +// Valid returns true if the BlockDevice structure is valid and complete. +func (blkdev BlockDevice) Valid() bool { + if blkdev.Driver == "" || blkdev.ID == "" || blkdev.File == "" { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of this block device. +func (blkdev BlockDevice) QemuParams(config *Config) []string { + var blkParams []string + var deviceParams []string + var qemuParams []string + + deviceParams = append(deviceParams, fmt.Sprintf("%s", blkdev.Driver)) + deviceParams = append(deviceParams, fmt.Sprintf(",drive=%s", blkdev.ID)) + if blkdev.SCSI == false { + deviceParams = append(deviceParams, ",scsi=off") + } + + if blkdev.WCE == false { + deviceParams = append(deviceParams, ",config-wce=off") + } + + blkParams = append(blkParams, fmt.Sprintf("id=%s", blkdev.ID)) + blkParams = append(blkParams, fmt.Sprintf(",file=%s", blkdev.File)) + blkParams = append(blkParams, fmt.Sprintf(",aio=%s", blkdev.AIO)) + blkParams = append(blkParams, fmt.Sprintf(",format=%s", blkdev.Format)) + blkParams = append(blkParams, fmt.Sprintf(",if=%s", blkdev.Interface)) + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + + qemuParams = append(qemuParams, "-drive") + qemuParams = append(qemuParams, strings.Join(blkParams, "")) + + return qemuParams +} + +// RTCBaseType is the qemu RTC base time type. +type RTCBaseType string + +// RTCClock is the qemu RTC clock type. +type RTCClock string + +// RTCDriftFix is the qemu RTC drift fix type. +type RTCDriftFix string + +const ( + // UTC is the UTC base time for qemu RTC. + UTC RTCBaseType = "utc" + + // LocalTime is the local base time for qemu RTC. + LocalTime = "localtime" +) + +const ( + // Host is for using the host clock as a reference. + Host RTCClock = "host" + + // VM is for using the guest clock as a reference + VM = "vm" +) + +const ( + // Slew is the qemu RTC Drift fix mechanism. + Slew RTCDriftFix = "slew" + + // NoDriftFix means we don't want/need to fix qemu's RTC drift. + NoDriftFix = "none" +) + +// RTC represents a qemu Real Time Clock configuration. +type RTC struct { + // Base is the RTC start time. + Base RTCBaseType + + // Clock is the is the RTC clock driver. + Clock RTCClock + + // DriftFix is the drift fixing mechanism. + DriftFix RTCDriftFix +} + +// Valid returns true if the RTC structure is valid and complete. +func (rtc RTC) Valid() bool { + if rtc.Clock != "" { + if rtc.Clock != Host && rtc.Clock != VM { + return false + } + } + + if rtc.DriftFix != "" { + if rtc.DriftFix != Slew && rtc.DriftFix != NoDriftFix { + return false + } + } + + return true +} + +// QMPSocketType is the type of socket used for QMP communication. +type QMPSocketType string + +const ( + // Unix socket for QMP. + Unix QMPSocketType = "unix" +) + +// QMPSocket represents a qemu QMP socket configuration. +type QMPSocket struct { + // Type is the socket type (e.g. "unix"). + Type QMPSocketType + + // 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 +} + +// Valid returns true if the QMPSocket structure is valid and complete. +func (qmp QMPSocket) Valid() bool { + if qmp.Type == "" || qmp.Name == "" { + return false + } + + if qmp.Type != Unix { + return false + } + + return true +} + +// 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 + + // Devices is a list of devices for qemu to create and drive. + Devices []Device + + // 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 + + qemuParams []string +} + +// appendFDs append a list of file descriptors to the qemu configuration and +// returns a slice of offset file descriptors that will be seen by the qemu process. +func (config *Config) appendFDs(fds []*os.File) []int { + var fdInts []int + + oldLen := len(config.fds) + + config.fds = append(config.fds, fds...) + + // The magic 3 offset comes from https://golang.org/src/os/exec/exec.go: + // ExtraFiles specifies additional open files to be inherited by the + // new process. It does not include standard input, standard output, or + // standard error. If non-nil, entry i becomes file descriptor 3+i. + for i := range fds { + fdInts = append(fdInts, oldLen+3+i) + } + + return fdInts +} + +func (config *Config) appendName() { + if config.Name != "" { + config.qemuParams = append(config.qemuParams, "-name") + config.qemuParams = append(config.qemuParams, config.Name) + } +} + +func (config *Config) appendMachine() { + 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)) + } + + config.qemuParams = append(config.qemuParams, "-machine") + config.qemuParams = append(config.qemuParams, strings.Join(machineParams, "")) + } +} + +func (config *Config) appendCPUModel() { + if config.CPUModel != "" { + config.qemuParams = append(config.qemuParams, "-cpu") + config.qemuParams = append(config.qemuParams, config.CPUModel) + } +} + +func (config *Config) appendQMPSocket() { + if config.QMPSocket.Valid() == false { + return + } + + 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") + } + } + + config.qemuParams = append(config.qemuParams, "-qmp") + config.qemuParams = append(config.qemuParams, strings.Join(qmpParams, "")) +} + +func (config *Config) appendDevices() { + for _, d := range config.Devices { + if d.Valid() == false { + continue + } + + config.qemuParams = append(config.qemuParams, d.QemuParams(config)...) + } +} + +func (config *Config) appendUUID() { + if config.UUID != "" { + config.qemuParams = append(config.qemuParams, "-uuid") + config.qemuParams = append(config.qemuParams, config.UUID) + } +} + +func (config *Config) appendMemory() { + 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)) + } + + config.qemuParams = append(config.qemuParams, "-m") + config.qemuParams = append(config.qemuParams, strings.Join(memoryParams, "")) + } +} + +func (config *Config) appendCPUs() { + 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)) + } + + config.qemuParams = append(config.qemuParams, "-smp") + config.qemuParams = append(config.qemuParams, strings.Join(SMPParams, "")) + } +} + +func (config *Config) appendRTC() { + if config.RTC.Valid() == false { + return + } + + var RTCParams []string + + RTCParams = append(RTCParams, fmt.Sprintf("base=%s", string(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)) + } + + config.qemuParams = append(config.qemuParams, "-rtc") + config.qemuParams = append(config.qemuParams, strings.Join(RTCParams, "")) +} + +func (config *Config) appendGlobalParam() { + if config.GlobalParam != "" { + config.qemuParams = append(config.qemuParams, "-global") + config.qemuParams = append(config.qemuParams, config.GlobalParam) + } +} + +func (config *Config) appendVGA() { + if config.VGA != "" { + config.qemuParams = append(config.qemuParams, "-vga") + config.qemuParams = append(config.qemuParams, config.VGA) + } +} + +func (config *Config) appendKernel() { + if config.Kernel.Path != "" { + config.qemuParams = append(config.qemuParams, "-kernel") + config.qemuParams = append(config.qemuParams, config.Kernel.Path) + + if config.Kernel.Params != "" { + config.qemuParams = append(config.qemuParams, "-append") + config.qemuParams = append(config.qemuParams, config.Kernel.Params) + } + } +} + +func (config *Config) appendKnobs() { + if config.Knobs.NoUserConfig == true { + config.qemuParams = append(config.qemuParams, "-no-user-config") + } + + if config.Knobs.NoDefaults == true { + config.qemuParams = append(config.qemuParams, "-nodefaults") + } + + if config.Knobs.NoGraphic == true { + config.qemuParams = append(config.qemuParams, "-nographic") + } +} + +// 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) { + config.appendName() + config.appendUUID() + config.appendMachine() + config.appendCPUModel() + config.appendQMPSocket() + config.appendMemory() + config.appendCPUs() + config.appendDevices() + config.appendRTC() + config.appendGlobalParam() + config.appendVGA() + config.appendKnobs() + config.appendKernel() + + return LaunchCustomQemu(config.Ctx, config.Path, config.qemuParams, 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 +// config.qemuParams 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. // @@ -48,13 +971,18 @@ import ( // 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(ctx context.Context, params []string, fds []*os.File, logger QMPLog) (string, error) { +func LaunchCustomQemu(ctx context.Context, path string, params []string, fds []*os.File, logger QMPLog) (string, error) { if logger == nil { logger = qmpNullLogger{} } errStr := "" - cmd := exec.Command("qemu-system-x86_64", params...) + + 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 diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go new file mode 100644 index 000000000..647b202a9 --- /dev/null +++ b/qemu/qemu_test.go @@ -0,0 +1,288 @@ +/* +// 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 + +import ( + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/01org/ciao/testutil" +) + +func testAppend(structure interface{}, expected string, t *testing.T) { + var config Config + + switch s := structure.(type) { + case Machine: + config.Machine = s + config.appendMachine() + + case Device: + config.Devices = []Device{s} + config.appendDevices() + + case Knobs: + config.Knobs = s + config.appendKnobs() + + case Kernel: + config.Kernel = s + config.appendKernel() + + case Memory: + config.Memory = s + config.appendMemory() + + case SMP: + config.SMP = s + config.appendCPUs() + + case QMPSocket: + config.QMPSocket = s + config.appendQMPSocket() + + case RTC: + config.RTC = s + config.appendRTC() + } + + result := strings.Join(config.qemuParams, " ") + if result != expected { + t.Fatalf("Failed to append parameters [%s] != [%s]", result, expected) + } +} + +var machineString = "-machine pc-lite,accel=kvm,kernel_irqchip,nvdimm" + +func TestAppendMachine(t *testing.T) { + machine := Machine{ + Type: "pc-lite", + Acceleration: "kvm,kernel_irqchip,nvdimm", + } + + testAppend(machine, machineString, t) +} + +func TestAppendEmptyMachine(t *testing.T) { + machine := Machine{} + + testAppend(machine, "", t) +} + +var deviceNVDIMMString = "-device nvdimm,id=nv0,memdev=mem0 -object memory-backend-file,id=mem0,mem-path=/root,size=65536" + +func TestAppendDeviceNVDIMM(t *testing.T) { + object := Object{ + Driver: NVDIMM, + Type: MemoryBackendFile, + DeviceID: "nv0", + ID: "mem0", + MemPath: "/root", + Size: 1 << 16, + } + + testAppend(object, deviceNVDIMMString, t) +} + +var deviceFSString = "-device virtio-9p-pci,fsdev=workload9p,mount_tag=rootfs -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security-model=none" + +func TestAppendDeviceFS(t *testing.T) { + fsdev := FSDevice{ + Driver: Virtio9P, + FSDriver: Local, + ID: "workload9p", + Path: "/var/lib/docker/devicemapper/mnt/e31ebda2", + MountTag: "rootfs", + SecurityModel: None, + } + + testAppend(fsdev, deviceFSString, t) +} + +var deviceNetworkString = "-device virtio-net,netdev=tap0,mac=01:02:de:ad:be:ef -netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=3:4,vhost=on" + +func TestAppendDeviceNetwork(t *testing.T) { + foo, _ := ioutil.TempFile(os.TempDir(), "qemu-ciao-test") + bar, _ := ioutil.TempFile(os.TempDir(), "qemu-ciao-test") + + defer os.Remove(foo.Name()) + defer os.Remove(bar.Name()) + + netdev := NetDevice{ + Driver: VirtioNet, + Type: TAP, + ID: "tap0", + IFName: "ceth0", + Script: "no", + DownScript: "no", + FDs: []*os.File{foo, bar}, + VHost: true, + MACAddress: "01:02:de:ad:be:ef", + } + + testAppend(netdev, deviceNetworkString, t) +} + +var deviceSerialString = "-device virtio-serial-pci,id=serial0" + +func TestAppendDeviceSerial(t *testing.T) { + sdev := SerialDevice{ + Driver: VirtioSerial, + ID: "serial0", + } + + testAppend(sdev, deviceSerialString, t) +} + +var deviceBlockString = "-device virtio-blk,drive=hd0,scsi=off,config-wce=off -drive id=hd0,file=/var/lib/ciao.img,aio=threads,format=qcow2,if=none" + +func TestAppendDeviceBlock(t *testing.T) { + blkdev := BlockDevice{ + Driver: VirtioBlock, + ID: "hd0", + File: "/var/lib/ciao.img", + AIO: Threads, + Format: QCOW2, + Interface: NoInterface, + SCSI: false, + WCE: false, + } + + testAppend(blkdev, deviceBlockString, t) +} + +func TestAppendEmptyDevice(t *testing.T) { + device := SerialDevice{} + + testAppend(device, "", t) +} + +var knobsString = "-no-user-config -nodefaults -nographic" + +func TestAppendKnobsAllTrue(t *testing.T) { + knobs := Knobs{ + NoUserConfig: true, + NoDefaults: true, + NoGraphic: true, + } + + testAppend(knobs, knobsString, t) +} + +func TestAppendKnobsAllFalse(t *testing.T) { + knobs := Knobs{ + NoUserConfig: false, + NoDefaults: false, + NoGraphic: false, + } + + testAppend(knobs, "", t) +} + +var kernelString = "-kernel /opt/vmlinux.container -append root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable" + +func TestAppendKernel(t *testing.T) { + kernel := Kernel{ + Path: "/opt/vmlinux.container", + Params: "root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable", + } + + testAppend(kernel, kernelString, t) +} + +var memoryString = "-m 2G,slots=2,maxmem=3G" + +func TestAppendMemory(t *testing.T) { + memory := Memory{ + Size: "2G", + Slots: 2, + MaxMem: "3G", + } + + testAppend(memory, memoryString, t) +} + +var cpusString = "-smp 2,cores=1,threads=2,sockets=2" + +func TestAppendCPUs(t *testing.T) { + smp := SMP{ + CPUs: 2, + Sockets: 2, + Cores: 1, + Threads: 2, + } + + testAppend(smp, cpusString, t) +} + +var qmpSocketServerString = "-qmp unix:cc-qmp,server,nowait" +var qmpSocketString = "-qmp unix:cc-qmp" + +func TestAppendQMPSocketServer(t *testing.T) { + qmp := QMPSocket{ + Type: "unix", + Name: "cc-qmp", + Server: true, + NoWait: true, + } + + testAppend(qmp, qmpSocketServerString, t) +} + +func TestAppendQMPSocket(t *testing.T) { + qmp := QMPSocket{ + Type: Unix, + Name: "cc-qmp", + Server: false, + } + + testAppend(qmp, qmpSocketString, t) +} + +var qemuString = "-name cc-qemu -cpu host -uuid " + testutil.AgentUUID + +func TestAppendStrings(t *testing.T) { + config := Config{ + Path: "qemu", + Name: "cc-qemu", + UUID: testutil.AgentUUID, + CPUModel: "host", + } + + config.appendName() + config.appendCPUModel() + config.appendUUID() + + result := strings.Join(config.qemuParams, " ") + if result != qemuString { + t.Fatalf("Failed to append parameters [%s] != [%s]", result, qemuString) + } +} + +var rtcString = "-rtc base=utc,driftfix=slew,clock=host" + +func TestAppendRTC(t *testing.T) { + rtc := RTC{ + Base: UTC, + Clock: Host, + DriftFix: Slew, + } + + testAppend(rtc, rtcString, t) +}